[Proposal] Global temporary tables
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>
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>
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
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/TodoIn 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.patchHowever, 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
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/TodoIn 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.patchHowever, 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 GTTthen 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
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>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>
(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>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 GTTthen 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/>
The Russian Postgres Company
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>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>
(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>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 GTTthen 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/>
The Russian Postgres Company
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 GTTthen 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
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 changesin 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^32and 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 significantlyvary.
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
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 GTTthen 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
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 significantlyvary.
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
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 GTTthen 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
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
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
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();
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 queryI support dividing them like this:
Patch #1: Support global temporary tables
Patch #2: Allow (all kinds of) temporary tables to be used by parallel queryThe 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
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
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
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;
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 cansignificantly 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;
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
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
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
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.
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.
I don't think - the extensions can use UNION and the content will be same
as caches used by planner.
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 thatanybody
is trying to manually interpret values in this table.
And optimizer is retrieving statistic through sys-cache mechanism andso
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.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.
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.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.
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.
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?
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.
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.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.
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.
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+
+ 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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+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
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+
+ 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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+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;
+
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 tableI 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>
č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 thisglobal 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>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+
+ 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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+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
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 tableI wonder why do you need such restriction?
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com <http://www.postgrespro.com/>
The Russian Postgres Company<global_temporary_table_v1-pg13.patch>
č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 attachedthis 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>
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
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+
+ 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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+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
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
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 GTTthen 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/>
The Russian Postgres Company
<global_private_temp-4.patch>
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
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
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
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
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
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
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
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
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 version2 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 dataI 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
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 versionOK, 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 dataNo 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
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 versionOK, 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 dataNo 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
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 version2 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 dataI 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
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
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 dataNo 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
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+
+ 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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ 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;
+
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 conditionsPlease 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
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 dataNo 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
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
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
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
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.
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
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 allThis 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 conditionsPlease 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 disconnectafter 100 sec, the table pg_attribute has 3.2MB
and 64 tps, 6446 transactiontest02.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 disconnectafter 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/>
[2] https://commitfest.postgresql.org/26/2233/ <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/>
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
ú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 conditionsPlease 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 disconnectafter 100 sec, the table pg_attribute has 3.2MB
and 64 tps, 6446 transactiontest02.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 disconnectafter 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/>
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
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 dataNo 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
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 allThis 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 conditionsPlease 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 disconnectafter 100 sec, the table pg_attribute has 3.2MB
and 64 tps, 6446 transactiontest02.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 disconnectafter 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/>
[2] https://commitfest.postgresql.org/26/2233/ <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/>
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
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
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
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 allThis 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 conditionsPlease 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 disconnectafter 100 sec, the table pg_attribute has 3.2MB
and 64 tps, 6446 transactiontest02.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 disconnectafter 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/>
[2] https://commitfest.postgresql.org/26/2233/ <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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+
+ 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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ 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;
+
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
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.shdescriptioninclude $(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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+
+ 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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ 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;
+
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 filesrc/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.shdescriptioninclude $(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
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 allThis 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 conditionsPlease 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 disconnectafter 100 sec, the table pg_attribute has 3.2MB
and 64 tps, 6446 transactiontest02.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 disconnectafter 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/>
[2] https://commitfest.postgresql.org/26/2233/ <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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+
+ 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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ 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;
+
ú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 conditionsPlease 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 disconnectafter 100 sec, the table pg_attribute has 3.2MB
and 64 tps, 6446 transactiontest02.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 disconnectafter 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/>
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
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.shdescriptioninclude $(top_srcdir)/src/backend/common.mk <http://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
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 allThis 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 conditionsPlease 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 tableIt 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 disconnectafter 100 sec, the table pg_attribute has 3.2MB
and 64 tps, 6446 transactiontest02.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 disconnectafter 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/>
[2] https://commitfest.postgresql.org/26/2233/ <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/>
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
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 conditionsPlease 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
messagepostgres=# drop table foo;
ERROR: can not drop index when other backend attached this global temp
tableIt 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 10000postgres=# \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 disconnectafter 100 sec, the table pg_attribute has 3.2MB
and 64 tps, 6446 transactiontest02.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 disconnectafter 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/>
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
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 allThis 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 conditionsPlease 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 tableIt 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 10000postgres=# \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 disconnectafter 100 sec, the table pg_attribute has 3.2MB
and 64 tps, 6446 transactiontest02.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 disconnectafter 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/>
[2] https://commitfest.postgresql.org/26/2233/ <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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+
+ 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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ 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;
+
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.shdescriptioninclude $(top_srcdir)/src/backend/common.mk <http://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
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+
+ 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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ 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;
+
č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 filesrc/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.shdescriptioninclude $(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
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
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
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
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
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
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 indexbuild
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
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
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 connectWhy? 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
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/>
The Russian Postgres Company
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
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
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
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+
+ 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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ 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;
+
ú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 CompanyOpinion 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 assertionPlease help me review.
Wenjing
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/>
The Enterprise PostgreSQL CompanyOpinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of field "rd_islocaltemp" is not probably best
I renamed rd_islocaltempI 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 assertionPlease 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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+
+ 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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ 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;
+
ú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 CompanyOpinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of
field "rd_islocaltemp" is not probably best
I renamed rd_islocaltempI 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 assertionPlease help me review.
Wenjing
ú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 CompanyOpinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name
of field "rd_islocaltemp" is not probably best
I renamed rd_islocaltempI don't see any change?
Rename rd_islocaltemp to rd_istemp
in global_temporary_table_v8-pg13.patchok :)
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 assertionPlease help me review.
Wenjing
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
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/>
The Enterprise PostgreSQL CompanyOpinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of field "rd_islocaltemp" is not probably best
I renamed rd_islocaltempI 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 assertionPlease help me review.
Wenjing
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
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_islocaltempI 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
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
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 statementThat 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
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
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
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
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
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
č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
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
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
č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
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
č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
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/>
The Enterprise PostgreSQL CompanyOpinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of field "rd_islocaltemp" is not probably best
I renamed rd_islocaltempI 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 assertionPlease 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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+ int natts = 0;
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temp table yet");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create stroage file", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temp relation table",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ elog(ERROR, "backend %d relid %u already exists in global temp table local hash",
+ MyBackendId, relid);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry->spcnode = rnode.spcNode;
+ entry->relpages = 0;
+ entry->reltuples = 0;
+ entry->relallvisible = 0;
+ entry->relkind = rel->rd_rel->relkind;
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->natts = natts;
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+ }
+
+ entry->relfrozenxid = RecentXmin;
+ entry->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ gtt_storage_checkin(rnode);
+ }
+ else
+ {
+ entry->on_commit_delete = false;
+ entry->relfrozenxid = 0;
+ entry->relminmxid = 0;
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid)
+{
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, NULL);
+
+ if (entry)
+ {
+ int i;
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ RelFileNode rnode;
+
+ rnode.spcNode = entry->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = entry->relid;
+
+ gtt_storage_checkout(rnode, false);
+
+ Assert(TransactionIdIsNormal(entry->relfrozenxid));
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->att_stat_tups[i])
+ {
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ }
+ }
+
+ pfree(entry->attnum);
+ pfree(entry->att_stat_tups);
+ }
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, &found);
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ int nrels = 0,
+ maxrels = 0;
+ SMgrRelation *srels = NULL;
+ RelFileNode *rnodes = NULL;
+ char *relkinds = NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+
+ rnode.spcNode = entry->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = entry->relid;
+
+ srel = smgropen(rnode, MyBackendId);
+
+ /* allocate the initial array, or extend it, if needed */
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxrels);
+ rnodes = palloc(sizeof(RelFileNode) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+ rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ srels[nrels] = srel;
+ rnodes[nrels] = rnode;
+ relkinds[nrels] = entry->relkind;
+ nrels++;
+ }
+
+ if (nrels > 0)
+ {
+ int i;
+
+ smgrdounlinkall(srels, nrels, false);
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ /* todo */
+ if (entry->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ Assert(entry->relid == reloid);
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == 0)
+ {
+ entry->attnum[i] = attnum;
+ break;
+ }
+ else if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < entry->natts);
+ Assert(entry->att_stat_tups[i] == NULL);
+ entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return NULL;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ return entry->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+ MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ HeapTuple tuple;
+ Relation rel = NULL;
+ int attnum = PG_GETARG_INT32(1);
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Tuplestorestate *tupstore;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ tuple = get_gtt_att_statistic(reloid, attnum, false);
+ if (tuple)
+ {
+ Datum values[31];
+ bool isnull[31];
+ HeapTuple res = NULL;
+
+ memset(&values, 0, sizeof(values));
+ memset(&isnull, 0, sizeof(isnull));
+ heap_deform_tuple(tuple, sd, values, isnull);
+ res = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, res);
+ }
+
+ table_close(rel, NoLock);
+ table_close(pg_tatistic, AccessShareLock);
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ if (get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid))
+ {
+ Datum values[5];
+ bool isnull[5];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(relpages);
+ values[1] = Float4GetDatum((float4)reltuples);
+ values[2] = Int32GetDatum(relallvisible);
+ values[3] = UInt32GetDatum(relfrozenxid);
+ values[4] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ map = copy_active_gtt_bitmap(rel->rd_node);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ int i = 0;
+ int j = 0;
+ uint32 oldest = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ if (max_active_gtt <= 0)
+ return (Datum) 0;
+
+ if (RecoveryInProgress())
+ return (Datum) 0;
+
+ pids = palloc0(sizeof(int) * num_xid);
+ xids = palloc0(sizeof(int) * num_xid);
+ oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+ if (i > 0)
+ {
+ if (i > 0)
+ {
+ pids[i] = 0;
+ xids[i] = oldest;
+ i++;
+ }
+
+ for(j = 0; j < i; j++)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(pids[j]);
+ values[1] = UInt32GetDatum(xids[j]);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ }
+ tuplestore_donestoring(tupstore);
+ pfree(pids);
+ pfree(xids);
+
+ return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(indexOid));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid relOid = index->rd_index->indrelid;
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ if (gtt_storage_attached(relOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c9e6060..d814ff2 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
WHERE NOT pg_is_other_temp_schema(N.oid)
AND relkind = 'S';
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stavalues1
+ WHEN s.stakind2 = 1 THEN s.stavalues2
+ WHEN s.stakind3 = 1 THEN s.stavalues3
+ WHEN s.stakind4 = 1 THEN s.stavalues4
+ WHEN s.stakind5 = 1 THEN s.stavalues5
+ END AS most_common_vals,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stanumbers1
+ WHEN s.stakind2 = 1 THEN s.stanumbers2
+ WHEN s.stakind3 = 1 THEN s.stanumbers3
+ WHEN s.stakind4 = 1 THEN s.stanumbers4
+ WHEN s.stakind5 = 1 THEN s.stanumbers5
+ END AS most_common_freqs,
+ CASE
+ WHEN s.stakind1 = 2 THEN s.stavalues1
+ WHEN s.stakind2 = 2 THEN s.stavalues2
+ WHEN s.stakind3 = 2 THEN s.stavalues3
+ WHEN s.stakind4 = 2 THEN s.stavalues4
+ WHEN s.stakind5 = 2 THEN s.stavalues5
+ END AS histogram_bounds,
+ CASE
+ WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+ WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+ WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+ WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+ WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+ END AS correlation,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stavalues1
+ WHEN s.stakind2 = 4 THEN s.stavalues2
+ WHEN s.stakind3 = 4 THEN s.stavalues3
+ WHEN s.stakind4 = 4 THEN s.stavalues4
+ WHEN s.stakind5 = 4 THEN s.stavalues5
+ END AS most_common_elems,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stanumbers1
+ WHEN s.stakind2 = 4 THEN s.stanumbers2
+ WHEN s.stakind3 = 4 THEN s.stanumbers3
+ WHEN s.stakind4 = 4 THEN s.stanumbers4
+ WHEN s.stakind5 = 4 THEN s.stanumbers5
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN s.stakind1 = 5 THEN s.stanumbers1
+ WHEN s.stakind2 = 5 THEN s.stanumbers2
+ WHEN s.stakind3 = 5 THEN s.stanumbers3
+ WHEN s.stakind4 = 5 THEN s.stanumbers4
+ WHEN s.stakind5 = 5 THEN s.stanumbers5
+ END AS elem_count_histogram
+ FROM
+ pg_class c
+ JOIN pg_attribute a ON c.oid = a.attrelid
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
CREATE VIEW pg_stats WITH (security_barrier) AS
SELECT
nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..977a984 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
#include "commands/dbcommands.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int acquire_inherited_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
static void update_attstats(Oid relid, bool inh,
- int natts, VacAttrStats **vacattrstats);
+ int natts, VacAttrStats **vacattrstats, char relpersistence);
static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
@@ -586,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* pg_statistic for columns we didn't process, we leave them alone.)
*/
update_attstats(RelationGetRelid(onerel), inh,
- attr_cnt, vacattrstats);
+ attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
for (ind = 0; ind < nindexes; ind++)
{
AnlIndexData *thisdata = &indexdata[ind];
update_attstats(RelationGetRelid(Irel[ind]), false,
- thisdata->attr_cnt, thisdata->vacattrstats);
+ thisdata->attr_cnt, thisdata->vacattrstats,
+ RelationGetRelPersistence(Irel[ind]));
}
/*
@@ -1456,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
* by taking a self-exclusive lock on the relation in analyze_rel().
*/
static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
{
Relation sd;
int attno;
@@ -1558,31 +1560,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
}
}
- /* Is there already a pg_statistic tuple for this attribute? */
- oldtup = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(relid),
- Int16GetDatum(stats->attr->attnum),
- BoolGetDatum(inh));
-
- if (HeapTupleIsValid(oldtup))
+ /* Update column statistic to localhash, not catalog */
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
{
- /* Yes, replace it */
- stup = heap_modify_tuple(oldtup,
- RelationGetDescr(sd),
- values,
- nulls,
- replaces);
- ReleaseSysCache(oldtup);
- CatalogTupleUpdate(sd, &stup->t_self, stup);
+ up_gtt_att_statistic(relid,
+ stats->attr->attnum,
+ inh,
+ natts,
+ RelationGetDescr(sd),
+ values,
+ nulls);
}
else
{
- /* No, insert new tuple */
- stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
- CatalogTupleInsert(sd, stup);
- }
+ /* Is there already a pg_statistic tuple for this attribute? */
+ oldtup = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(relid),
+ Int16GetDatum(stats->attr->attnum),
+ BoolGetDatum(inh));
- heap_freetuple(stup);
+ if (HeapTupleIsValid(oldtup))
+ {
+ /* Yes, replace it */
+ stup = heap_modify_tuple(oldtup,
+ RelationGetDescr(sd),
+ values,
+ nulls,
+ replaces);
+ ReleaseSysCache(oldtup);
+ CatalogTupleUpdate(sd, &stup->t_self, stup);
+ }
+ else
+ {
+ /* No, insert new tuple */
+ stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+ CatalogTupleInsert(sd, stup);
+ }
+
+ heap_freetuple(stup);
+ }
}
table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index e9d7a7f..3088e26 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
errmsg("cannot vacuum temporary tables of other sessions")));
}
+ /* not support cluster global temp table yet */
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("not support cluster global temporary tables yet")));
+
/*
* Also check for active uses of the relation in the current transaction,
* including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index ec20ba3..a29cc00 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2614,6 +2614,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
!isTempNamespace(classtuple->relnamespace))
continue;
+ /* not support reindex on global temp table, so skip it */
+ if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ ereport(WARNING,
+ (errmsg("global temp table \"%s.%s\" skip reindexed",
+ get_namespace_name(get_rel_namespace(relid)),
+ get_rel_name(relid))));
+ continue;
+ }
+
/* Check user/system classification, and optionally skip */
if (objectKind == REINDEX_OBJECT_SYSTEM &&
!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 329ab84..dfa257c 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* transaction.
*/
relpersistence = get_rel_persistence(relid);
- if (relpersistence == RELPERSISTENCE_TEMP)
+ if (relpersistence == RELPERSISTENCE_TEMP ||
+ relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..44f350d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
*/
static SeqTableData *last_used_seq = NULL;
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
static Relation lock_and_open_sequence(SeqTable seq);
static void create_seq_hashtable(void);
static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +108,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
List **owned_by);
static void do_setval(Oid relid, int64 next, bool iscalled);
static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
/*
@@ -222,7 +223,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
/* now initialize the sequence's data */
tuple = heap_form_tuple(tupDesc, value, null);
- fill_seq_with_data(rel, tuple);
+ fill_seq_with_data(rel, tuple, InvalidBuffer);
/* process OWNED BY if given */
if (owned_by)
@@ -327,7 +328,7 @@ ResetSequence(Oid seq_relid)
/*
* Insert the modified tuple into the new storage file.
*/
- fill_seq_with_data(seq_rel, tuple);
+ fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
/* Clear local cache so that we don't think we have cached numbers */
/* Note that we do not change the currval() state */
@@ -340,17 +341,21 @@ ResetSequence(Oid seq_relid)
* Initialize a sequence's relation with the specified tuple as content
*/
static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
{
- Buffer buf;
Page page;
sequence_magic *sm;
OffsetNumber offnum;
+ bool lockBuffer = false;
/* Initialize first page of relation with special magic number */
- buf = ReadBuffer(rel, P_NEW);
- Assert(BufferGetBlockNumber(buf) == 0);
+ if (buf == InvalidBuffer)
+ {
+ buf = ReadBuffer(rel, P_NEW);
+ Assert(BufferGetBlockNumber(buf) == 0);
+ lockBuffer = true;
+ }
page = BufferGetPage(buf);
@@ -360,7 +365,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
/* Now insert sequence tuple */
- LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+ if (lockBuffer)
+ LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
/*
* Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
END_CRIT_SECTION();
- UnlockReleaseBuffer(buf);
+ if (lockBuffer)
+ UnlockReleaseBuffer(buf);
}
/*
@@ -502,7 +509,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
/*
* Insert the modified tuple into the new storage file.
*/
- fill_seq_with_data(seqrel, newdatatuple);
+ fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
}
/* process OWNED BY if given */
@@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
page = BufferGetPage(*buf);
+ if (GlobalTempRelationPageIsNotInitialized(rel, page))
+ {
+ /* Initialize sequence for global temporary tables */
+ Datum value[SEQ_COL_LASTCOL] = {0};
+ bool null[SEQ_COL_LASTCOL] = {false};
+ HeapTuple tuple;
+ int64 startv = get_seqence_start_value(RelationGetRelid(rel));
+
+ /*
+ * last_value from pg_sequence.seqstart
+ * log_cnt = 0
+ * is_called = false
+ */
+ value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+ tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+ fill_seq_with_data(rel, tuple, *buf);
+ heap_freetuple(tuple);
+ }
sm = (sequence_magic *) PageGetSpecialPointer(page);
if (sm->magic != SEQ_MAGIC)
@@ -1954,3 +1980,23 @@ seq_mask(char *page, BlockNumber blkno)
mask_unused_space(page);
}
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+ HeapTuple seqtuple;
+ Form_pg_sequence seqform;
+ int64 start;
+
+ seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+ if (!HeapTupleIsValid(seqtuple))
+ elog(ERROR, "cache lookup failed for sequence %u",
+ seqid);
+
+ seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+ start = seqform->seqstart;
+ ReleaseSysCache(seqtuple);
+
+ return start;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 70589dd..81bd3e5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
#include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
#include "commands/cluster.h"
#include "commands/comment.h"
#include "commands/defrem.h"
@@ -552,6 +553,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
Relation partitionTbl);
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static bool has_oncommit_option(List *options);
/* ----------------------------------------------------------------
@@ -597,6 +599,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
LOCKMODE parentLockmode;
const char *accessMethod = NULL;
Oid accessMethodId = InvalidOid;
+ bool has_oncommit_clause = false;
/*
* Truncate relname to appropriate length (probably a waste of time, as
@@ -607,8 +610,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Check consistency of arguments
*/
+ /* global temp table same as local temp table */
if (stmt->oncommit != ONCOMMIT_NOOP
- && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+ && !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+ stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
@@ -638,7 +643,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* code. This is needed because calling code might not expect untrusted
* tables to appear in pg_temp at the front of its search path.
*/
- if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+ /* global temp table same as local temp table */
+ if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+ stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
&& InSecurityRestrictedOperation())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -739,6 +746,57 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Parse and validate reloptions, if any.
*/
+ /* global temp table */
+ has_oncommit_clause = has_oncommit_option(stmt->options);
+ if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+ {
+ /* inherit table or parition table inherit on commit property from parent table*/
+ if (inheritOids && stmt->oncommit == ONCOMMIT_NOOP && !has_oncommit_clause)
+ {
+ Oid parent = linitial_oid(inheritOids);
+ Relation relation = table_open(parent, NoLock);
+
+ if (!RELATION_IS_GLOBAL_TEMP(relation))
+ elog(ERROR, "The parent table must be global temporary table");
+
+ if (RELATION_GTT_ON_COMMIT_DELETE(relation))
+ stmt->oncommit = ONCOMMIT_DELETE_ROWS;
+ else
+ stmt->oncommit = ONCOMMIT_PRESERVE_ROWS;
+ table_close(relation, NoLock);
+ }
+
+ if (has_oncommit_clause)
+ {
+ if (stmt->oncommit != ONCOMMIT_NOOP)
+ elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+ }
+ else
+ {
+ DefElem *opt = makeNode(DefElem);
+
+ opt->type = T_DefElem;
+ opt->defnamespace = NULL;
+ opt->defname = "on_commit_delete_rows";
+ opt->defaction = DEFELEM_UNSPEC;
+
+ /* use reloptions to remember on commit clause */
+ if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+ opt->arg = (Node *)makeString("true");
+ else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+ opt->arg = (Node *)makeString("false");
+ else if (stmt->oncommit == ONCOMMIT_NOOP)
+ opt->arg = (Node *)makeString("false");
+ else
+ elog(ERROR, "global temp table not support on commit drop clause");
+
+ stmt->options = lappend(stmt->options, opt);
+ }
+ }
+ else if (has_oncommit_clause)
+ elog(ERROR, "regular table cannot specifie on_commit_delete_rows");
+
reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
true, false);
@@ -1816,7 +1874,8 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
* table or the current physical file to be thrown away anyway.
*/
if (rel->rd_createSubid == mySubid ||
- rel->rd_newRelfilenodeSubid == mySubid)
+ rel->rd_newRelfilenodeSubid == mySubid ||
+ RELATION_IS_GLOBAL_TEMP(rel))
{
/* Immediate, non-rollbackable truncation is OK */
heap_truncate_one_rel(rel);
@@ -3375,6 +3434,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
* specially.
*/
targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+ if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+ {
+ if (is_other_backend_use_gtt(targetrelation->rd_node))
+ elog(ERROR, "can not rename relation when other backend attached this global temp table");
+ }
+
namespaceId = RelationGetNamespace(targetrelation);
/*
@@ -3550,6 +3616,13 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
/* Caller is required to provide an adequate lock. */
rel = relation_open(context->relid, NoLock);
+ /* We allow to alter global temp table only this session use it */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (is_other_backend_use_gtt(rel->rd_node))
+ elog(ERROR, "can not alter relation when other backend attached this global temp table");
+ }
+
CheckTableNotInUse(rel, "ALTER TABLE");
ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8123,6 +8196,13 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
errmsg("referenced relation \"%s\" is not a table",
RelationGetRelationName(pkrel))));
+ /* global temp table not support foreign key constraint yet */
+ if (RELATION_IS_GLOBAL_TEMP(pkrel))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("referenced relation \"%s\" is not a global temp table",
+ RelationGetRelationName(pkrel))));
+
if (!allowSystemTableMods && IsSystemRelation(pkrel))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -8162,6 +8242,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("constraints on temporary tables must involve temporary tables of this session")));
break;
+ /* global temp table not support foreign key constraint yet */
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("not support foreign key constraints on global temp table yet")));
+ break;
}
/*
@@ -13199,7 +13285,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
* NOTE: any conflict in relfilenode value will be caught in
* RelationCreateStorage().
*/
- RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+ RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
/* copy main fork */
RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14606,7 +14692,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
*/
switch (rel->rd_rel->relpersistence)
{
+ /* global temp table same as local temp table */
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_GLOBAL_TEMP:
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17246,3 +17334,20 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
table_close(rel, NoLock);
}
}
+
+static bool
+has_oncommit_option(List *options)
+{
+ ListCell *listptr;
+
+ foreach(listptr, options)
+ {
+ DefElem *def = (DefElem *) lfirst(listptr);
+
+ if (pg_strcasecmp(def->defname, "on_commit_delete_rows") == 0)
+ return true;
+ }
+
+ return false;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..69ad24f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
HeapTuple ctup;
Form_pg_class pgcform;
bool dirty;
+ bool is_gtt = false;
+
+ /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+ if (RELATION_IS_GLOBAL_TEMP(relation))
+ {
+ is_gtt = true;
+ up_gtt_relstats(relation,
+ num_pages, num_tuples,
+ num_all_visible_pages,
+ frozenxid, minmulti);
+ }
rd = table_open(RelationRelationId, RowExclusiveLock);
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
/* Apply statistical updates, if any, to copied tuple */
dirty = false;
- if (pgcform->relpages != (int32) num_pages)
+
+ if (is_gtt)
+ relation->rd_rel->relpages = (int32) num_pages;
+ else if (pgcform->relpages != (int32) num_pages)
{
pgcform->relpages = (int32) num_pages;
dirty = true;
}
- if (pgcform->reltuples != (float4) num_tuples)
+
+ if (is_gtt)
+ relation->rd_rel->reltuples = (float4) num_tuples;
+ else if (pgcform->reltuples != (float4) num_tuples)
{
pgcform->reltuples = (float4) num_tuples;
dirty = true;
}
- if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+ if (is_gtt)
+ relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+ else if (pgcform->relallvisible != (int32) num_all_visible_pages)
{
pgcform->relallvisible = (int32) num_all_visible_pages;
dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
* This should match vac_update_datfrozenxid() concerning what we consider
* to be "in the future".
*/
- if (TransactionIdIsNormal(frozenxid) &&
+ if (!is_gtt &&
+ TransactionIdIsNormal(frozenxid) &&
pgcform->relfrozenxid != frozenxid &&
(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
}
/* Similarly for relminmxid */
- if (MultiXactIdIsValid(minmulti) &&
+ if (!is_gtt &&
+ MultiXactIdIsValid(minmulti) &&
pgcform->relminmxid != minmulti &&
(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
continue;
}
+ /* global temp table relstats not in pg_class */
+ if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+
/*
* Some table AMs might not need per-relation xid / multixid horizons.
* It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
Assert(TransactionIdIsNormal(newFrozenXid));
Assert(MultiXactIdIsValid(newMinMulti));
+ /*
+ * Global temp table get frozenxid from MyProc
+ * to avoid the vacuum truncate clog that gtt need.
+ */
+ if (max_active_gtt > 0)
+ {
+ TransactionId oldest_gtt_frozenxid =
+ list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+ if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+ TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+ {
+ ereport(WARNING,
+ (errmsg("global temp table oldest FrozenXid is far in the past"),
+ errhint("please truncate them or kill those sessions that use them.")));
+ newFrozenXid = oldest_gtt_frozenxid;
+ }
+ }
+
/* Now fetch the pg_database tuple we need to update. */
relation = table_open(DatabaseRelationId, RowExclusiveLock);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ee5c3a6..0d7a2d4 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
if (isTempNamespace(get_rel_namespace(rte->relid)))
continue;
+ if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+
PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
}
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 8286d9c..c3992a4 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte)
{
+ char relpersistence;
+
/*
* The flag has previously been initialized to false, so we can just
* return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* the rest of the necessary infrastructure right now anyway. So
* for now, bail out if we see a temporary table.
*/
- if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+ /* global temp table is same as local temp table */
+ relpersistence = get_rel_persistence(rte->relid);
+
+ if (relpersistence == RELPERSISTENCE_TEMP ||
+ relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
return;
/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d6f2153..310a9e2 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6312,7 +6312,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
* Furthermore, any index predicate or index expressions must be parallel
* safe.
*/
+ /* global temp table is same as local temp table */
if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+ heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
{
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 748bebf..a5ddbcd 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2579,6 +2579,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("materialized views must not use temporary tables or views")));
+ if (is_query_using_gtt(query))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialized views must not use global temporary tables or views")));
+
/*
* A materialized view would either need to save parameters for use in
* maintaining/loading the data or prohibit them entirely. The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1b0edf5..492639f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp: TEMPORARY { $$ = RELPERSISTENCE_TEMP; }
| LOCAL TEMP { $$ = RELPERSISTENCE_TEMP; }
| GLOBAL TEMPORARY
{
- ereport(WARNING,
- (errmsg("GLOBAL is deprecated in temporary table creation"),
- parser_errposition(@1)));
- $$ = RELPERSISTENCE_TEMP;
+ $$ = RELPERSISTENCE_GLOBAL_TEMP;
}
| GLOBAL TEMP
{
- ereport(WARNING,
- (errmsg("GLOBAL is deprecated in temporary table creation"),
- parser_errposition(@1)));
- $$ = RELPERSISTENCE_TEMP;
+ $$ = RELPERSISTENCE_GLOBAL_TEMP;
}
| UNLOGGED { $$ = RELPERSISTENCE_UNLOGGED; }
| /*EMPTY*/ { $$ = RELPERSISTENCE_PERMANENT; }
@@ -11591,19 +11585,13 @@ OptTempTableName:
}
| GLOBAL TEMPORARY opt_table qualified_name
{
- ereport(WARNING,
- (errmsg("GLOBAL is deprecated in temporary table creation"),
- parser_errposition(@1)));
$$ = $4;
- $$->relpersistence = RELPERSISTENCE_TEMP;
+ $$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
}
| GLOBAL TEMP opt_table qualified_name
{
- ereport(WARNING,
- (errmsg("GLOBAL is deprecated in temporary table creation"),
- parser_errposition(@1)));
$$ = $4;
- $$->relpersistence = RELPERSISTENCE_TEMP;
+ $$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
}
| UNLOGGED opt_table qualified_name
{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
List **colnames, List **colvars);
static int specialAttNum(const char *attname);
static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
/*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
isQueryUsingTempRelation_walker,
context);
}
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Query))
+ {
+ Query *query = (Query *) node;
+ ListCell *rtable;
+
+ foreach(rtable, query->rtable)
+ {
+ RangeTblEntry *rte = lfirst(rtable);
+
+ if (rte->rtekind == RTE_RELATION)
+ {
+ Relation rel = relation_open(rte->relid, AccessShareLock);
+ char relpersistence = rel->rd_rel->relpersistence;
+
+ relation_close(rel, AccessShareLock);
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ return true;
+ }
+ }
+
+ return query_tree_walker(query,
+ is_query_using_gtt_walker,
+ context,
+ QTW_IGNORE_JOINALIASES);
+ }
+
+ return expression_tree_walker(node,
+ is_query_using_gtt_walker,
+ context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+ return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee2d2b5..9c9abaa 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
seqstmt->options = seqoptions;
+ if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
/*
* If a sequence data type was specified, add it to the options. Prepend
* to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6d1f28c..ed837d1 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2086,6 +2086,11 @@ do_autovacuum(void)
}
continue;
}
+ else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* autovacuum skip vacuum global temp table */
+ continue;
+ }
/* Fetch reloptions and the pgstat entry for this table */
relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2152,7 +2157,9 @@ do_autovacuum(void)
/*
* We cannot safely process other backends' temp tables, so skip 'em.
*/
- if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+ /* autovacuum skip vacuum global temp table */
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP ||
+ classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
continue;
relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index aba3960..3c4b96c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
#include "executor/instrument.h"
#include "lib/binaryheap.h"
#include "miscadmin.h"
@@ -52,6 +53,7 @@
#include "utils/rel.h"
#include "utils/resowner_private.h"
#include "utils/timestamp.h"
+#include "utils/guc.h"
/* Note: these two macros only work on shared buffers, not local ones! */
@@ -432,7 +434,7 @@ ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref)
static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence,
ForkNumber forkNum, BlockNumber blockNum,
ReadBufferMode mode, BufferAccessStrategy strategy,
- bool *hit);
+ bool *hit, Relation rel);
static bool PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy);
static void PinBuffer_Locked(BufferDesc *buf);
static void UnpinBuffer(BufferDesc *buf, bool fixOwner);
@@ -664,7 +666,8 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
*/
pgstat_count_buffer_read(reln);
buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence,
- forkNum, blockNum, mode, strategy, &hit);
+ forkNum, blockNum, mode, strategy, &hit,
+ reln);
if (hit)
pgstat_count_buffer_hit(reln);
return buf;
@@ -692,7 +695,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
Assert(InRecovery);
return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum,
- mode, strategy, &hit);
+ mode, strategy, &hit, NULL);
}
@@ -704,7 +707,8 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
static Buffer
ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
BlockNumber blockNum, ReadBufferMode mode,
- BufferAccessStrategy strategy, bool *hit)
+ BufferAccessStrategy strategy, bool *hit,
+ Relation rel)
{
BufferDesc *bufHdr;
Block bufBlock;
@@ -719,6 +723,15 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
isExtend = (blockNum == P_NEW);
+ /* create storage when first read data page for gtt */
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+ (isExtend || blockNum == 0) &&
+ forkNum == MAIN_FORKNUM &&
+ !gtt_storage_attached(smgr->smgr_rnode.node.relNode))
+ {
+ RelationCreateStorage(smgr->smgr_rnode.node, relpersistence, rel);
+ }
+
TRACE_POSTGRESQL_BUFFER_READ_START(forkNum, blockNum,
smgr->smgr_rnode.node.spcNode,
smgr->smgr_rnode.node.dbNode,
@@ -2809,6 +2822,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
BlockNumber
RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
{
+ /*
+ * When this backend not init gtt storage
+ * return 0
+ */
+ if (RELATION_IS_GLOBAL_TEMP(relation) &&
+ !gtt_storage_attached(relation->rd_node.relNode))
+ {
+ return 0;
+ }
+
switch (relation->rd_rel->relkind)
{
case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
#include "access/nbtree.h"
#include "access/subtrans.h"
#include "access/twophase.h"
+#include "catalog/storage_gtt.h"
#include "commands/async.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
size = add_size(size, BTreeShmemSize());
size = add_size(size, SyncScanShmemSize());
size = add_size(size, AsyncShmemSize());
+ size = add_size(size, active_gtt_shared_hash_size());
#ifdef EXEC_BACKEND
size = add_size(size, ShmemBackendArraySize());
#endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
SUBTRANSShmemInit();
MultiXactShmemInit();
InitBufferPool();
+ /* global temporary table */
+ active_gtt_shared_hash_init();
/*
* Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index c3adb2e..eb95e5f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
#include "utils/builtins.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
+#include "utils/guc.h"
#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var))))
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
LWLockRelease(ProcArrayLock);
}
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+ ProcArrayStruct *arrayP = procArray;
+ TransactionId result = InvalidTransactionId;
+ int index;
+ int flags = 0;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return 0;
+
+ if (max_size > 0)
+ {
+ Assert(pids);
+ Assert(xids);
+ Assert(n);
+ *n = 0;
+ }
+
+ if (max_active_gtt <= 0)
+ return InvalidTransactionId;
+
+ if (RecoveryInProgress())
+ return InvalidTransactionId;
+
+ flags |= PROC_IS_AUTOVACUUM;
+ flags |= PROC_IN_LOGICAL_DECODING;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+ if (max_size > 0 && max_size < arrayP->numProcs)
+ {
+ LWLockRelease(ProcArrayLock);
+ elog(ERROR, "list_all_gtt_frozenxids require more array");
+ }
+
+ for (index = 0; index < arrayP->numProcs; index++)
+ {
+ int pgprocno = arrayP->pgprocnos[index];
+ volatile PGPROC *proc = &allProcs[pgprocno];
+ volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+ if (pgxact->vacuumFlags & flags)
+ continue;
+
+ if (proc->databaseId == MyDatabaseId &&
+ TransactionIdIsNormal(proc->session_gtt_frozenxid))
+ {
+ if (result == InvalidTransactionId)
+ result = proc->session_gtt_frozenxid;
+ else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+ result = proc->session_gtt_frozenxid;
+
+ if (max_size > 0)
+ {
+ pids[i] = proc->pid;
+ xids[i] = proc->session_gtt_frozenxid;
+ i++;
+ }
+ }
+ }
+ LWLockRelease(ProcArrayLock);
+
+ if (max_size > 0)
+ *n = i;
+
+ return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 32df8c8..e4e1125 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->tempNamespaceId = InvalidOid;
+ MyProc->session_gtt_frozenxid = InvalidTransactionId; /* init session level gtt frozenxid */
MyProc->isBackgroundWorker = IsBackgroundWorker;
MyPgXact->delayChkpt = false;
MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->tempNamespaceId = InvalidOid;
+ MyProc->session_gtt_frozenxid = InvalidTransactionId; /* init session level gtt frozenxid */
MyProc->isBackgroundWorker = IsBackgroundWorker;
MyPgXact->delayChkpt = false;
MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index c5b771c..e7725ce 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -646,6 +646,12 @@ mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
*/
if (zero_damaged_pages || InRecovery)
MemSet(buffer, 0, BLCKSZ);
+ else if(SmgrIsTemp(reln) && blocknum == 0 && forknum == MAIN_FORKNUM)
+ {
+ /* global temp table init btree meta page */
+ MemSet(buffer, 0, BLCKSZ);
+ mdwrite(reln, forknum, blocknum, buffer, true);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
Assert(backend != InvalidBackendId);
}
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ backend = BackendIdForTempRelations();
+ break;
default:
elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 7c6f057..eb10cf2 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
#include "executor/nodeAgg.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -4578,12 +4579,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
}
else if (index->indpred == NIL)
{
- vardata->statsTuple =
- SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(index->indexoid),
- Int16GetDatum(pos + 1),
- BoolGetDatum(false));
- vardata->freefunc = ReleaseSysCache;
+ char rel_persistence = get_rel_persistence(index->indexoid);
+
+ if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata->statsTuple =
+ get_gtt_att_statistic(index->indexoid,
+ Int16GetDatum(pos + 1),
+ false);
+ vardata->freefunc = release_gtt_statistic_cache;
+ }
+ else
+ {
+ vardata->statsTuple =
+ SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(index->indexoid),
+ Int16GetDatum(pos + 1),
+ BoolGetDatum(false));
+ vardata->freefunc = ReleaseSysCache;
+ }
if (HeapTupleIsValid(vardata->statsTuple))
{
@@ -4708,15 +4722,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
}
else if (rte->rtekind == RTE_RELATION)
{
- /*
- * Plain table or parent of an inheritance appendrel, so look up the
- * column in pg_statistic
- */
- vardata->statsTuple = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(rte->relid),
- Int16GetDatum(var->varattno),
- BoolGetDatum(rte->inh));
- vardata->freefunc = ReleaseSysCache;
+ char rel_persistence = get_rel_persistence(rte->relid);
+
+ if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+ var->varattno,
+ rte->inh);
+ vardata->freefunc = release_gtt_statistic_cache;
+ }
+ else
+ {
+ /*
+ * Plain table or parent of an inheritance appendrel, so look up the
+ * column in pg_statistic
+ */
+ vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(rte->relid),
+ Int16GetDatum(var->varattno),
+ BoolGetDatum(rte->inh));
+ vardata->freefunc = ReleaseSysCache;
+ }
if (HeapTupleIsValid(vardata->statsTuple))
{
@@ -6098,6 +6124,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
{
/* Simple variable --- look to stats for the underlying table */
RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+ char rel_persistence = get_rel_persistence(rte->relid);
Assert(rte->rtekind == RTE_RELATION);
relid = rte->relid;
@@ -6115,6 +6142,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata.statsTuple = get_gtt_att_statistic(relid,
+ colnum,
+ rte->inh);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6126,6 +6160,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
}
else
{
+ char rel_persistence = get_rel_persistence(index->indexoid);
+
/* Expression --- maybe there are stats for the index itself */
relid = index->indexoid;
colnum = 1;
@@ -6141,6 +6177,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata.statsTuple = get_gtt_att_statistic(relid,
+ colnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7054,6 +7097,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
/* attempt to lookup stats in relation for this index column */
if (attnum != 0)
{
+ char rel_persistence = get_rel_persistence(rte->relid);
+
/* Simple variable -- look to stats for the underlying table */
if (get_relation_stats_hook &&
(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7066,6 +7111,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
elog(ERROR,
"no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata.statsTuple =
+ get_gtt_att_statistic(rte->relid,
+ attnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple =
@@ -7078,6 +7131,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
}
else
{
+ char rel_persistence = get_rel_persistence(index->indexoid);
+
/*
* Looks like we've found an expression column in the index. Let's
* see if there's any stats for it.
@@ -7097,6 +7152,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata.statsTuple =
+ get_gtt_att_statistic(index->indexoid,
+ attnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1e3e6d3..345e3e9 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
#include "catalog/pg_statistic.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
if (stawidth > 0)
return stawidth;
}
+ if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ tp = get_gtt_att_statistic(relid, attnum, false);
+ if (!HeapTupleIsValid(tp))
+ return 0;
+
+ stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+ if (stawidth > 0)
+ return stawidth;
+ else
+ return 0;
+ }
tp = SearchSysCache3(STATRELATTINH,
ObjectIdGetDatum(relid),
Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index df025a5..8df9c7c 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
#include "catalog/pg_type.h"
#include "catalog/schemapg.h"
#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
#include "commands/policy.h"
#include "commands/trigger.h"
#include "miscadmin.h"
@@ -1124,6 +1125,29 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_islocaltemp = false;
}
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ {
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+
+ relation->rd_backend = BackendIdForTempRelations();
+ /*
+ * For global temp table, all backend can use
+ * this relation, so rd_islocaltemp is true
+ * in every backend.
+ */
+ relation->rd_islocaltemp = true;
+ get_gtt_relstats(RelationGetRelid(relation),
+ &relpages,
+ &reltuples,
+ &relallvisible,
+ NULL, NULL);
+ relation->rd_rel->relpages = (int32)relpages;
+ relation->rd_rel->reltuples = (float4)reltuples;
+ relation->rd_rel->relallvisible = (int32)relallvisible;
+ }
+ break;
default:
elog(ERROR, "invalid relpersistence: %c",
relation->rd_rel->relpersistence);
@@ -1178,6 +1202,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
case RELKIND_PARTITIONED_INDEX:
Assert(relation->rd_rel->relam != InvalidOid);
RelationInitIndexAccessInfo(relation);
+ gtt_fix_index_state(relation);
break;
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
@@ -2217,6 +2242,8 @@ RelationReloadIndexInfo(Relation relation)
HeapTupleHeaderGetXmin(tuple->t_data));
ReleaseSysCache(tuple);
+
+ gtt_fix_index_state(relation);
}
/* Okay, now it's valid again */
@@ -3313,6 +3340,15 @@ RelationBuildLocalRelation(const char *relname,
rel->rd_backend = BackendIdForTempRelations();
rel->rd_islocaltemp = true;
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ rel->rd_backend = BackendIdForTempRelations();
+ /*
+ * For global temp table, all backend can use
+ * this relation, so rd_islocaltemp is true
+ * in every backend.
+ */
+ rel->rd_islocaltemp = true;
+ break;
default:
elog(ERROR, "invalid relpersistence: %c", relpersistence);
break;
@@ -3427,6 +3463,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
TransactionId freezeXid = InvalidTransactionId;
RelFileNode newrnode;
+ if (RELATION_IS_GLOBAL_TEMP(relation))
+ elog(ERROR, "global temp table does not allow setting new relfilenode");
+
/* Allocate a new relfilenode */
newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
persistence);
@@ -3467,7 +3506,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
/* handle these directly, at least for now */
SMgrRelation srel;
- srel = RelationCreateStorage(newrnode, persistence);
+ srel = RelationCreateStorage(newrnode, persistence, relation);
smgrclose(srel);
}
break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index cacbe90..9c4220b 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -140,6 +140,18 @@ char *GUC_check_errmsg_string;
char *GUC_check_errdetail_string;
char *GUC_check_errhint_string;
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define MIN_NUM_ACTIVE_GTT 0
+#define DEFAULT_NUM_ACTIVE_GTT 1000
+#define MAX_NUM_ACTIVE_GTT 1000000
+
+int max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2012,6 +2024,15 @@ static struct config_bool ConfigureNamesBool[] =
static struct config_int ConfigureNamesInt[] =
{
{
+ {"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+ gettext_noop("max active global temporary table."),
+ NULL
+ },
+ &max_active_gtt,
+ DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+ NULL, NULL, NULL
+ },
+ {
{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
gettext_noop("Forces a switch to the next WAL file if a "
"new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ec3e2c6..d3697d2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15583,6 +15583,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
{
char *ftoptions = NULL;
char *srvname = NULL;
+ char *table_type = NULL;
switch (tbinfo->relkind)
{
@@ -15634,9 +15635,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
binary_upgrade_set_pg_class_oids(fout, q,
tbinfo->dobj.catId.oid, false);
+ if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+ table_type = "UNLOGGED ";
+ else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ table_type = "GLOBAL TEMPORARY ";
+ else
+ table_type = "";
+
appendPQExpBuffer(q, "CREATE %s%s %s",
- tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
- "UNLOGGED " : "",
+ table_type,
reltypename,
qualrelname);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
if (pset.sversion >= 90100)
{
appendPQExpBuffer(&buf,
- ",\n CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+ ",\n CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+ gettext_noop("session"),
gettext_noop("permanent"),
gettext_noop("temporary"),
gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index dc03fbd..8bd6d09 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1016,6 +1016,8 @@ static const pgsql_thing_t words_after_create[] = {
{"FOREIGN TABLE", NULL, NULL, NULL},
{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
{"GROUP", Query_for_list_of_roles},
+ {"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+ * ... */
{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
{"LANGUAGE", Query_for_list_of_languages},
{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2378,6 +2380,9 @@ psql_completion(const char *text, int start, int end)
/* CREATE FOREIGN DATA WRAPPER */
else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+ /* CREATE GLOBAL TEMP/TEMPORARY*/
+ else if (Matches("CREATE", "GLOBAL"))
+ COMPLETE_WITH("TEMP", "TEMPORARY");
/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2586,6 +2591,8 @@ psql_completion(const char *text, int start, int end)
/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+ else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+ COMPLETE_WITH("TABLE");
/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
else if (TailMatches("CREATE", "UNLOGGED"))
COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index a12fc1f..78958e4 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -165,6 +165,7 @@ typedef FormData_pg_class *Form_pg_class;
#define RELPERSISTENCE_PERMANENT 'p' /* regular table */
#define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */
#define RELPERSISTENCE_TEMP 't' /* temporary table */
+#define RELPERSISTENCE_GLOBAL_TEMP 'g' /* global temporary table */
/* default selection for replica identity (primary key or nothing) */
#define REPLICA_IDENTITY_DEFAULT 'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2228256..4d5d13a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5504,6 +5504,40 @@
proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
prosrc => 'pg_stat_get_xact_function_self_time' },
+# For global temporary table
+{ oid => '4191',
+ descr => 'List local statistics for global temporary table',
+ proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+ proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+ proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+ proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+ prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+ descr => 'List local relstats for global temporary table',
+ proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+ proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+ proargmodes => '{i,o,o,o,o,o}',
+ proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+ prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+ descr => 'List attached pid for one global temporary table',
+ proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+ proallargtypes => '{oid,oid,int4}',
+ proargmodes => '{i,o,o}',
+ proargnames => '{relid,relid,pid}',
+ prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+ descr => 'List those backends that have used global temporary table',
+ proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+ proallargtypes => '{int4,xid}',
+ proargmodes => '{o,o}',
+ proargnames => '{pid,relfrozenxid}',
+ prosrc => 'pg_list_gtt_relfrozenxids' },
+
{ oid => '3788',
descr => 'statistics: timestamp of the current statistics snapshot',
proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
#include "storage/smgr.h"
#include "utils/relcache.h"
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
extern void RelationDropStorage(Relation rel);
extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..aa80cb5
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,41 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ * prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+
+#endif /* STORAGE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid attnumTypeId(Relation rd, int attid);
extern Oid attnumCollationId(Relation rd, int attid);
extern bool isQueryUsingTempRelation(Query *query);
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
#endif /* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 870ecb5..92c590e 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
#define PageClearPrunable(page) \
(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+ ((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
/* ----------------------------------------------------------------
* extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_TBM,
LWTRANCHE_PARALLEL_APPEND,
LWTRANCHE_SXACT,
+ LWTRANCHE_GTT_CTL,
LWTRANCHE_FIRST_USER_DEFINED
} BuiltinTrancheIds;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
Oid tempNamespaceId; /* OID of temp schema this backend is
* using */
+ TransactionId session_gtt_frozenxid; /* session level global temp table relfrozenxid */
+
bool isBackgroundWorker; /* true if background worker. */
/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
TransactionId *catalog_xmin);
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
#endif /* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int tcp_user_timeout;
extern bool trace_sort;
#endif
+/* global temporary table */
+extern int max_active_gtt;
+/* end */
+
/*
* Functions exported by guc.c
*/
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..b308cb8 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
struct SMgrRelationData *rd_smgr; /* cached file handle, or NULL */
int rd_refcnt; /* reference count */
BackendId rd_backend; /* owning backend id, if temporary relation */
- bool rd_islocaltemp; /* rel is a temp rel of this session */
+ bool rd_islocaltemp; /* rel is a temp rel of this session */
bool rd_isnailed; /* rel is nailed in cache */
bool rd_isvalid; /* relcache entry is valid */
bool rd_indexvalid; /* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
int parallel_workers; /* max number of parallel workers */
bool vacuum_index_cleanup; /* enables index vacuuming and cleanup */
bool vacuum_truncate; /* enables vacuum to truncate a relation */
+ bool on_commit_delete_rows; /* global temp table */
} StdRdOptions;
#define HEAP_MIN_FILLFACTOR 10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
* True if relation's pages are stored in local buffers.
*/
#define RelationUsesLocalBuffers(relation) \
- ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
/*
* RELATION_IS_LOCAL
- * If a rel is either temp or newly created in the current transaction,
+ * If a rel is either local temp or global temp relation
+ * or newly created in the current transaction,
* it can be assumed to be accessible only to the current backend.
* This is typically used to decide that we can skip acquiring locks.
*
@@ -602,6 +605,17 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation) ((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation) \
+ ((relation)->rd_options && \
+ ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+ ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..e21e540
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,282 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+ product_no integer PRIMARY KEY,
+ name text,
+ price numeric
+);
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR: cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR: not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR: ON COMMIT can only be used on temporary tables
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR: regular table cannot specifie on_commit_delete_rows
+-- ok
+CREATE global temp TABLE measurement (
+ logdate date not null,
+ peaktemp int,
+ unitsales int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count
+-------
+ 2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count
+-------
+ 0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR: The parent table must be global temporary table
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname;
+ relname | relkind | relpersistence | reloptions
+---------------------------------+---------+----------------+-------------------------------
+ p_table01 | p | g | {on_commit_delete_rows=true}
+ p_table01_2017 | r | g | {on_commit_delete_rows=true}
+ p_table01_2018 | r | g | {on_commit_delete_rows=true}
+ p_table01_id_seq | S | g |
+ p_table02 | p | g | {on_commit_delete_rows=false}
+ p_table02_2017 | r | g | {on_commit_delete_rows=false}
+ p_table02_2018 | r | g | {on_commit_delete_rows=false}
+ p_table02_id_seq | S | g |
+ tbl_inherits_parent | r | p |
+ tbl_inherits_parent_global_temp | r | g | {on_commit_delete_rows=true}
+ tbl_inherits_partition | r | g | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR: global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR: can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+ERROR: referenced relation "products" is not a global temp table
+-- ERROR
+CREATE global temp TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+ERROR: referenced relation "products" is not a global temp table
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+ id SERIAL PRIMARY KEY,
+ data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a
+----+---
+ 2 | 1
+ 3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a
+----+---
+ 4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR: materialized views must not use global temporary tables or views
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+ERROR: only support btree index on global temp table
+create index idx_err on gtt1 using gist (a);
+ERROR: data type integer has no default operator class for access method "gist"
+HINT: You must specify an operator class for the index or define a default operator class for the data type.
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+ERROR: only support btree index on global temp table
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+ERROR: only support btree index on global temp table
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain select * from gt1 where a=1;
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on gt1 (cost=12.17..482.50 rows=500 width=4)
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on idx_gt1_1 (cost=0.00..12.04 rows=500 width=0)
+ Index Cond: (a = 1)
+(4 rows)
+
+explain select * from gt1 where a=200000;
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on gt1 (cost=12.17..482.50 rows=500 width=4)
+ Recheck Cond: (a = 200000)
+ -> Bitmap Index Scan on idx_gt1_1 (cost=0.00..12.04 rows=500 width=0)
+ Index Cond: (a = 200000)
+(4 rows)
+
+explain select * from gt1 where a*10=300;
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on gt1 (cost=12.29..483.88 rows=500 width=4)
+ Recheck Cond: ((a * 10) = 300)
+ -> Bitmap Index Scan on idx_gt1_3 (cost=0.00..12.17 rows=500 width=0)
+ Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain select * from gt1 where a*10=3;
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on gt1 (cost=12.29..483.88 rows=500 width=4)
+ Recheck Cond: ((a * 10) = 3)
+ -> Bitmap Index Scan on idx_gt1_3 (cost=0.00..12.17 rows=500 width=0)
+ Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain select * from gt1 where a=1;
+ QUERY PLAN
+--------------------------------------------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1 (cost=0.29..8.31 rows=1 width=4)
+ Index Cond: (a = 1)
+(2 rows)
+
+explain select * from gt1 where a=200000;
+ QUERY PLAN
+--------------------------------------------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1 (cost=0.29..8.31 rows=1 width=4)
+ Index Cond: (a = 200000)
+(2 rows)
+
+explain select * from gt1 where a*10=300;
+ QUERY PLAN
+---------------------------------------------------------------------
+ Index Scan using idx_gt1_3 on gt1 (cost=0.42..8.44 rows=1 width=4)
+ Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain select * from gt1 where a*10=3;
+ QUERY PLAN
+---------------------------------------------------------------------
+ Index Scan using idx_gt1_3 on gt1 (cost=0.42..8.44 rows=1 width=4)
+ Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE: drop cascades to 17 other objects
+DETAIL: drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..30d8a7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,84 @@
+set search_path=gtt,sys;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..850ef3e
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,161 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+ QUERY PLAN
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+ Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+ relname | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon | 450560 | 8192 | 499712 | 475136 | 974848
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+ relname
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+ relname | pg_relation_size | pg_table_size | pg_total_relation_size
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 | 65536 | 65536 | 65536
+ idx_gtt_t_kenyon_2 | 409600 | 409600 | 409600
+(2 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..9fe5fd4
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,9 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..b258b7c
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 0
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible
+------------+-----------+----------+-----------+---------------
+ gtt_stats | gtt | 0 | 0 | 0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible
+------------+-----------+----------+-----------+---------------
+ gtt_stats | gtt | 55 | 10000 | 0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats | gtt | a | f | 0 | 4 | -1 | | | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} | 1 | | |
+ gtt_stats | gtt | b | f | 0 | 5 | 1 | {test} | {1} | | 1 | | |
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE: drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2ab2115..4f4eb46 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
FROM pg_authid
WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.relid,
+ s.pid
+ FROM (pg_class c
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.relpages,
+ s.reltuples,
+ s.relallvisible,
+ s.relfrozenxid,
+ s.relminmxid
+ FROM (pg_class c
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN (s.stakind1 = 1) THEN s.stavalues1
+ WHEN (s.stakind2 = 1) THEN s.stavalues2
+ WHEN (s.stakind3 = 1) THEN s.stavalues3
+ WHEN (s.stakind4 = 1) THEN s.stavalues4
+ WHEN (s.stakind5 = 1) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS most_common_vals,
+ CASE
+ WHEN (s.stakind1 = 1) THEN s.stanumbers1
+ WHEN (s.stakind2 = 1) THEN s.stanumbers2
+ WHEN (s.stakind3 = 1) THEN s.stanumbers3
+ WHEN (s.stakind4 = 1) THEN s.stanumbers4
+ WHEN (s.stakind5 = 1) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS most_common_freqs,
+ CASE
+ WHEN (s.stakind1 = 2) THEN s.stavalues1
+ WHEN (s.stakind2 = 2) THEN s.stavalues2
+ WHEN (s.stakind3 = 2) THEN s.stavalues3
+ WHEN (s.stakind4 = 2) THEN s.stavalues4
+ WHEN (s.stakind5 = 2) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS histogram_bounds,
+ CASE
+ WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+ WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+ WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+ WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+ WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+ ELSE NULL::real
+ END AS correlation,
+ CASE
+ WHEN (s.stakind1 = 4) THEN s.stavalues1
+ WHEN (s.stakind2 = 4) THEN s.stavalues2
+ WHEN (s.stakind3 = 4) THEN s.stavalues3
+ WHEN (s.stakind4 = 4) THEN s.stavalues4
+ WHEN (s.stakind5 = 4) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS most_common_elems,
+ CASE
+ WHEN (s.stakind1 = 4) THEN s.stanumbers1
+ WHEN (s.stakind2 = 4) THEN s.stanumbers2
+ WHEN (s.stakind3 = 4) THEN s.stanumbers3
+ WHEN (s.stakind4 = 4) THEN s.stanumbers4
+ WHEN (s.stakind5 = 4) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN (s.stakind1 = 5) THEN s.stanumbers1
+ WHEN (s.stakind2 = 5) THEN s.stanumbers2
+ WHEN (s.stakind3 = 5) THEN s.stanumbers3
+ WHEN (s.stakind4 = 5) THEN s.stanumbers4
+ WHEN (s.stakind5 = 5) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS elem_count_histogram
+ FROM ((pg_class c
+ JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
pg_hba_file_rules| SELECT a.line_number,
a.type,
a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
# run stats by itself because its delay may be insufficient under heavy load
test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..784c537
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,187 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+ product_no integer PRIMARY KEY,
+ name text,
+ price numeric
+);
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+ logdate date not null,
+ peaktemp int,
+ unitsales int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+
+-- ERROR
+CREATE global temp TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+ id SERIAL PRIMARY KEY,
+ data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+create index idx_err on gtt1 using gist (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain select * from gt1 where a=1;
+explain select * from gt1 where a=200000;
+explain select * from gt1 where a*10=300;
+explain select * from gt1 where a*10=3;
+analyze gt1;
+explain select * from gt1 where a=1;
+explain select * from gt1 where a=200000;
+explain select * from gt1 where a*10=300;
+explain select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..5203c2b
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,76 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..2f4d883
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,16 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
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_islocaltempI 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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+ int natts = 0;
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temp table yet");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create stroage file", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temp relation table",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ elog(ERROR, "backend %d relid %u already exists in global temp table local hash",
+ MyBackendId, relid);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry->spcnode = rnode.spcNode;
+ entry->relpages = 0;
+ entry->reltuples = 0;
+ entry->relallvisible = 0;
+ entry->relkind = rel->rd_rel->relkind;
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->natts = natts;
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+ }
+
+ entry->relfrozenxid = RecentXmin;
+ entry->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ gtt_storage_checkin(rnode);
+ }
+ else
+ {
+ entry->on_commit_delete = false;
+ entry->relfrozenxid = 0;
+ entry->relminmxid = 0;
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid)
+{
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, NULL);
+
+ if (entry)
+ {
+ int i;
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ RelFileNode rnode;
+
+ rnode.spcNode = entry->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = entry->relid;
+
+ gtt_storage_checkout(rnode, false);
+
+ Assert(TransactionIdIsNormal(entry->relfrozenxid));
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->att_stat_tups[i])
+ {
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ }
+ }
+
+ pfree(entry->attnum);
+ pfree(entry->att_stat_tups);
+ }
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, &found);
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ int nrels = 0,
+ maxrels = 0;
+ SMgrRelation *srels = NULL;
+ RelFileNode *rnodes = NULL;
+ char *relkinds = NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+
+ rnode.spcNode = entry->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = entry->relid;
+
+ srel = smgropen(rnode, MyBackendId);
+
+ /* allocate the initial array, or extend it, if needed */
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxrels);
+ rnodes = palloc(sizeof(RelFileNode) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+ rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ srels[nrels] = srel;
+ rnodes[nrels] = rnode;
+ relkinds[nrels] = entry->relkind;
+ nrels++;
+ }
+
+ if (nrels > 0)
+ {
+ int i;
+
+ smgrdounlinkall(srels, nrels, false);
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ /* todo */
+ if (entry->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ Assert(entry->relid == reloid);
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == 0)
+ {
+ entry->attnum[i] = attnum;
+ break;
+ }
+ else if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < entry->natts);
+ Assert(entry->att_stat_tups[i] == NULL);
+ entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return NULL;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ return entry->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+ MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ HeapTuple tuple;
+ Relation rel = NULL;
+ int attnum = PG_GETARG_INT32(1);
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Tuplestorestate *tupstore;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ tuple = get_gtt_att_statistic(reloid, attnum, false);
+ if (tuple)
+ {
+ Datum values[31];
+ bool isnull[31];
+ HeapTuple res = NULL;
+
+ memset(&values, 0, sizeof(values));
+ memset(&isnull, 0, sizeof(isnull));
+ heap_deform_tuple(tuple, sd, values, isnull);
+ res = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, res);
+ }
+
+ table_close(rel, NoLock);
+ table_close(pg_tatistic, AccessShareLock);
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ if (get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid))
+ {
+ Datum values[5];
+ bool isnull[5];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(relpages);
+ values[1] = Float4GetDatum((float4)reltuples);
+ values[2] = Int32GetDatum(relallvisible);
+ values[3] = UInt32GetDatum(relfrozenxid);
+ values[4] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ map = copy_active_gtt_bitmap(rel->rd_node);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ int i = 0;
+ int j = 0;
+ uint32 oldest = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ if (max_active_gtt <= 0)
+ return (Datum) 0;
+
+ if (RecoveryInProgress())
+ return (Datum) 0;
+
+ pids = palloc0(sizeof(int) * num_xid);
+ xids = palloc0(sizeof(int) * num_xid);
+ oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+ if (i > 0)
+ {
+ if (i > 0)
+ {
+ pids[i] = 0;
+ xids[i] = oldest;
+ i++;
+ }
+
+ for(j = 0; j < i; j++)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(pids[j]);
+ values[1] = UInt32GetDatum(xids[j]);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ }
+ tuplestore_donestoring(tupstore);
+ pfree(pids);
+ pfree(xids);
+
+ return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(indexOid));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid relOid = index->rd_index->indrelid;
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ if (gtt_storage_attached(relOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c9e6060..d814ff2 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
WHERE NOT pg_is_other_temp_schema(N.oid)
AND relkind = 'S';
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stavalues1
+ WHEN s.stakind2 = 1 THEN s.stavalues2
+ WHEN s.stakind3 = 1 THEN s.stavalues3
+ WHEN s.stakind4 = 1 THEN s.stavalues4
+ WHEN s.stakind5 = 1 THEN s.stavalues5
+ END AS most_common_vals,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stanumbers1
+ WHEN s.stakind2 = 1 THEN s.stanumbers2
+ WHEN s.stakind3 = 1 THEN s.stanumbers3
+ WHEN s.stakind4 = 1 THEN s.stanumbers4
+ WHEN s.stakind5 = 1 THEN s.stanumbers5
+ END AS most_common_freqs,
+ CASE
+ WHEN s.stakind1 = 2 THEN s.stavalues1
+ WHEN s.stakind2 = 2 THEN s.stavalues2
+ WHEN s.stakind3 = 2 THEN s.stavalues3
+ WHEN s.stakind4 = 2 THEN s.stavalues4
+ WHEN s.stakind5 = 2 THEN s.stavalues5
+ END AS histogram_bounds,
+ CASE
+ WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+ WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+ WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+ WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+ WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+ END AS correlation,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stavalues1
+ WHEN s.stakind2 = 4 THEN s.stavalues2
+ WHEN s.stakind3 = 4 THEN s.stavalues3
+ WHEN s.stakind4 = 4 THEN s.stavalues4
+ WHEN s.stakind5 = 4 THEN s.stavalues5
+ END AS most_common_elems,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stanumbers1
+ WHEN s.stakind2 = 4 THEN s.stanumbers2
+ WHEN s.stakind3 = 4 THEN s.stanumbers3
+ WHEN s.stakind4 = 4 THEN s.stanumbers4
+ WHEN s.stakind5 = 4 THEN s.stanumbers5
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN s.stakind1 = 5 THEN s.stanumbers1
+ WHEN s.stakind2 = 5 THEN s.stanumbers2
+ WHEN s.stakind3 = 5 THEN s.stanumbers3
+ WHEN s.stakind4 = 5 THEN s.stanumbers4
+ WHEN s.stakind5 = 5 THEN s.stanumbers5
+ END AS elem_count_histogram
+ FROM
+ pg_class c
+ JOIN pg_attribute a ON c.oid = a.attrelid
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
CREATE VIEW pg_stats WITH (security_barrier) AS
SELECT
nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..977a984 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
#include "commands/dbcommands.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int acquire_inherited_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
static void update_attstats(Oid relid, bool inh,
- int natts, VacAttrStats **vacattrstats);
+ int natts, VacAttrStats **vacattrstats, char relpersistence);
static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
@@ -586,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* pg_statistic for columns we didn't process, we leave them alone.)
*/
update_attstats(RelationGetRelid(onerel), inh,
- attr_cnt, vacattrstats);
+ attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
for (ind = 0; ind < nindexes; ind++)
{
AnlIndexData *thisdata = &indexdata[ind];
update_attstats(RelationGetRelid(Irel[ind]), false,
- thisdata->attr_cnt, thisdata->vacattrstats);
+ thisdata->attr_cnt, thisdata->vacattrstats,
+ RelationGetRelPersistence(Irel[ind]));
}
/*
@@ -1456,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
* by taking a self-exclusive lock on the relation in analyze_rel().
*/
static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
{
Relation sd;
int attno;
@@ -1558,31 +1560,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
}
}
- /* Is there already a pg_statistic tuple for this attribute? */
- oldtup = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(relid),
- Int16GetDatum(stats->attr->attnum),
- BoolGetDatum(inh));
-
- if (HeapTupleIsValid(oldtup))
+ /* Update column statistic to localhash, not catalog */
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
{
- /* Yes, replace it */
- stup = heap_modify_tuple(oldtup,
- RelationGetDescr(sd),
- values,
- nulls,
- replaces);
- ReleaseSysCache(oldtup);
- CatalogTupleUpdate(sd, &stup->t_self, stup);
+ up_gtt_att_statistic(relid,
+ stats->attr->attnum,
+ inh,
+ natts,
+ RelationGetDescr(sd),
+ values,
+ nulls);
}
else
{
- /* No, insert new tuple */
- stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
- CatalogTupleInsert(sd, stup);
- }
+ /* Is there already a pg_statistic tuple for this attribute? */
+ oldtup = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(relid),
+ Int16GetDatum(stats->attr->attnum),
+ BoolGetDatum(inh));
- heap_freetuple(stup);
+ if (HeapTupleIsValid(oldtup))
+ {
+ /* Yes, replace it */
+ stup = heap_modify_tuple(oldtup,
+ RelationGetDescr(sd),
+ values,
+ nulls,
+ replaces);
+ ReleaseSysCache(oldtup);
+ CatalogTupleUpdate(sd, &stup->t_self, stup);
+ }
+ else
+ {
+ /* No, insert new tuple */
+ stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+ CatalogTupleInsert(sd, stup);
+ }
+
+ heap_freetuple(stup);
+ }
}
table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index e9d7a7f..3088e26 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
errmsg("cannot vacuum temporary tables of other sessions")));
}
+ /* not support cluster global temp table yet */
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("not support cluster global temporary tables yet")));
+
/*
* Also check for active uses of the relation in the current transaction,
* including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index ec20ba3..a29cc00 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2614,6 +2614,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
!isTempNamespace(classtuple->relnamespace))
continue;
+ /* not support reindex on global temp table, so skip it */
+ if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ ereport(WARNING,
+ (errmsg("global temp table \"%s.%s\" skip reindexed",
+ get_namespace_name(get_rel_namespace(relid)),
+ get_rel_name(relid))));
+ continue;
+ }
+
/* Check user/system classification, and optionally skip */
if (objectKind == REINDEX_OBJECT_SYSTEM &&
!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 329ab84..dfa257c 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* transaction.
*/
relpersistence = get_rel_persistence(relid);
- if (relpersistence == RELPERSISTENCE_TEMP)
+ if (relpersistence == RELPERSISTENCE_TEMP ||
+ relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..44f350d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
*/
static SeqTableData *last_used_seq = NULL;
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
static Relation lock_and_open_sequence(SeqTable seq);
static void create_seq_hashtable(void);
static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +108,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
List **owned_by);
static void do_setval(Oid relid, int64 next, bool iscalled);
static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
/*
@@ -222,7 +223,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
/* now initialize the sequence's data */
tuple = heap_form_tuple(tupDesc, value, null);
- fill_seq_with_data(rel, tuple);
+ fill_seq_with_data(rel, tuple, InvalidBuffer);
/* process OWNED BY if given */
if (owned_by)
@@ -327,7 +328,7 @@ ResetSequence(Oid seq_relid)
/*
* Insert the modified tuple into the new storage file.
*/
- fill_seq_with_data(seq_rel, tuple);
+ fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
/* Clear local cache so that we don't think we have cached numbers */
/* Note that we do not change the currval() state */
@@ -340,17 +341,21 @@ ResetSequence(Oid seq_relid)
* Initialize a sequence's relation with the specified tuple as content
*/
static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
{
- Buffer buf;
Page page;
sequence_magic *sm;
OffsetNumber offnum;
+ bool lockBuffer = false;
/* Initialize first page of relation with special magic number */
- buf = ReadBuffer(rel, P_NEW);
- Assert(BufferGetBlockNumber(buf) == 0);
+ if (buf == InvalidBuffer)
+ {
+ buf = ReadBuffer(rel, P_NEW);
+ Assert(BufferGetBlockNumber(buf) == 0);
+ lockBuffer = true;
+ }
page = BufferGetPage(buf);
@@ -360,7 +365,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
/* Now insert sequence tuple */
- LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+ if (lockBuffer)
+ LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
/*
* Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
END_CRIT_SECTION();
- UnlockReleaseBuffer(buf);
+ if (lockBuffer)
+ UnlockReleaseBuffer(buf);
}
/*
@@ -502,7 +509,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
/*
* Insert the modified tuple into the new storage file.
*/
- fill_seq_with_data(seqrel, newdatatuple);
+ fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
}
/* process OWNED BY if given */
@@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
page = BufferGetPage(*buf);
+ if (GlobalTempRelationPageIsNotInitialized(rel, page))
+ {
+ /* Initialize sequence for global temporary tables */
+ Datum value[SEQ_COL_LASTCOL] = {0};
+ bool null[SEQ_COL_LASTCOL] = {false};
+ HeapTuple tuple;
+ int64 startv = get_seqence_start_value(RelationGetRelid(rel));
+
+ /*
+ * last_value from pg_sequence.seqstart
+ * log_cnt = 0
+ * is_called = false
+ */
+ value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+ tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+ fill_seq_with_data(rel, tuple, *buf);
+ heap_freetuple(tuple);
+ }
sm = (sequence_magic *) PageGetSpecialPointer(page);
if (sm->magic != SEQ_MAGIC)
@@ -1954,3 +1980,23 @@ seq_mask(char *page, BlockNumber blkno)
mask_unused_space(page);
}
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+ HeapTuple seqtuple;
+ Form_pg_sequence seqform;
+ int64 start;
+
+ seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+ if (!HeapTupleIsValid(seqtuple))
+ elog(ERROR, "cache lookup failed for sequence %u",
+ seqid);
+
+ seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+ start = seqform->seqstart;
+ ReleaseSysCache(seqtuple);
+
+ return start;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 70589dd..81bd3e5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
#include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
#include "commands/cluster.h"
#include "commands/comment.h"
#include "commands/defrem.h"
@@ -552,6 +553,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
Relation partitionTbl);
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static bool has_oncommit_option(List *options);
/* ----------------------------------------------------------------
@@ -597,6 +599,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
LOCKMODE parentLockmode;
const char *accessMethod = NULL;
Oid accessMethodId = InvalidOid;
+ bool has_oncommit_clause = false;
/*
* Truncate relname to appropriate length (probably a waste of time, as
@@ -607,8 +610,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Check consistency of arguments
*/
+ /* global temp table same as local temp table */
if (stmt->oncommit != ONCOMMIT_NOOP
- && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+ && !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+ stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
@@ -638,7 +643,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* code. This is needed because calling code might not expect untrusted
* tables to appear in pg_temp at the front of its search path.
*/
- if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+ /* global temp table same as local temp table */
+ if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+ stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
&& InSecurityRestrictedOperation())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -739,6 +746,57 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Parse and validate reloptions, if any.
*/
+ /* global temp table */
+ has_oncommit_clause = has_oncommit_option(stmt->options);
+ if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+ {
+ /* inherit table or parition table inherit on commit property from parent table*/
+ if (inheritOids && stmt->oncommit == ONCOMMIT_NOOP && !has_oncommit_clause)
+ {
+ Oid parent = linitial_oid(inheritOids);
+ Relation relation = table_open(parent, NoLock);
+
+ if (!RELATION_IS_GLOBAL_TEMP(relation))
+ elog(ERROR, "The parent table must be global temporary table");
+
+ if (RELATION_GTT_ON_COMMIT_DELETE(relation))
+ stmt->oncommit = ONCOMMIT_DELETE_ROWS;
+ else
+ stmt->oncommit = ONCOMMIT_PRESERVE_ROWS;
+ table_close(relation, NoLock);
+ }
+
+ if (has_oncommit_clause)
+ {
+ if (stmt->oncommit != ONCOMMIT_NOOP)
+ elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+ }
+ else
+ {
+ DefElem *opt = makeNode(DefElem);
+
+ opt->type = T_DefElem;
+ opt->defnamespace = NULL;
+ opt->defname = "on_commit_delete_rows";
+ opt->defaction = DEFELEM_UNSPEC;
+
+ /* use reloptions to remember on commit clause */
+ if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+ opt->arg = (Node *)makeString("true");
+ else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+ opt->arg = (Node *)makeString("false");
+ else if (stmt->oncommit == ONCOMMIT_NOOP)
+ opt->arg = (Node *)makeString("false");
+ else
+ elog(ERROR, "global temp table not support on commit drop clause");
+
+ stmt->options = lappend(stmt->options, opt);
+ }
+ }
+ else if (has_oncommit_clause)
+ elog(ERROR, "regular table cannot specifie on_commit_delete_rows");
+
reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
true, false);
@@ -1816,7 +1874,8 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
* table or the current physical file to be thrown away anyway.
*/
if (rel->rd_createSubid == mySubid ||
- rel->rd_newRelfilenodeSubid == mySubid)
+ rel->rd_newRelfilenodeSubid == mySubid ||
+ RELATION_IS_GLOBAL_TEMP(rel))
{
/* Immediate, non-rollbackable truncation is OK */
heap_truncate_one_rel(rel);
@@ -3375,6 +3434,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
* specially.
*/
targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+ if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+ {
+ if (is_other_backend_use_gtt(targetrelation->rd_node))
+ elog(ERROR, "can not rename relation when other backend attached this global temp table");
+ }
+
namespaceId = RelationGetNamespace(targetrelation);
/*
@@ -3550,6 +3616,13 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
/* Caller is required to provide an adequate lock. */
rel = relation_open(context->relid, NoLock);
+ /* We allow to alter global temp table only this session use it */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (is_other_backend_use_gtt(rel->rd_node))
+ elog(ERROR, "can not alter relation when other backend attached this global temp table");
+ }
+
CheckTableNotInUse(rel, "ALTER TABLE");
ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8123,6 +8196,13 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
errmsg("referenced relation \"%s\" is not a table",
RelationGetRelationName(pkrel))));
+ /* global temp table not support foreign key constraint yet */
+ if (RELATION_IS_GLOBAL_TEMP(pkrel))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("referenced relation \"%s\" is not a global temp table",
+ RelationGetRelationName(pkrel))));
+
if (!allowSystemTableMods && IsSystemRelation(pkrel))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -8162,6 +8242,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("constraints on temporary tables must involve temporary tables of this session")));
break;
+ /* global temp table not support foreign key constraint yet */
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("not support foreign key constraints on global temp table yet")));
+ break;
}
/*
@@ -13199,7 +13285,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
* NOTE: any conflict in relfilenode value will be caught in
* RelationCreateStorage().
*/
- RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+ RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
/* copy main fork */
RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14606,7 +14692,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
*/
switch (rel->rd_rel->relpersistence)
{
+ /* global temp table same as local temp table */
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_GLOBAL_TEMP:
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17246,3 +17334,20 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
table_close(rel, NoLock);
}
}
+
+static bool
+has_oncommit_option(List *options)
+{
+ ListCell *listptr;
+
+ foreach(listptr, options)
+ {
+ DefElem *def = (DefElem *) lfirst(listptr);
+
+ if (pg_strcasecmp(def->defname, "on_commit_delete_rows") == 0)
+ return true;
+ }
+
+ return false;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..69ad24f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
HeapTuple ctup;
Form_pg_class pgcform;
bool dirty;
+ bool is_gtt = false;
+
+ /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+ if (RELATION_IS_GLOBAL_TEMP(relation))
+ {
+ is_gtt = true;
+ up_gtt_relstats(relation,
+ num_pages, num_tuples,
+ num_all_visible_pages,
+ frozenxid, minmulti);
+ }
rd = table_open(RelationRelationId, RowExclusiveLock);
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
/* Apply statistical updates, if any, to copied tuple */
dirty = false;
- if (pgcform->relpages != (int32) num_pages)
+
+ if (is_gtt)
+ relation->rd_rel->relpages = (int32) num_pages;
+ else if (pgcform->relpages != (int32) num_pages)
{
pgcform->relpages = (int32) num_pages;
dirty = true;
}
- if (pgcform->reltuples != (float4) num_tuples)
+
+ if (is_gtt)
+ relation->rd_rel->reltuples = (float4) num_tuples;
+ else if (pgcform->reltuples != (float4) num_tuples)
{
pgcform->reltuples = (float4) num_tuples;
dirty = true;
}
- if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+ if (is_gtt)
+ relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+ else if (pgcform->relallvisible != (int32) num_all_visible_pages)
{
pgcform->relallvisible = (int32) num_all_visible_pages;
dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
* This should match vac_update_datfrozenxid() concerning what we consider
* to be "in the future".
*/
- if (TransactionIdIsNormal(frozenxid) &&
+ if (!is_gtt &&
+ TransactionIdIsNormal(frozenxid) &&
pgcform->relfrozenxid != frozenxid &&
(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
}
/* Similarly for relminmxid */
- if (MultiXactIdIsValid(minmulti) &&
+ if (!is_gtt &&
+ MultiXactIdIsValid(minmulti) &&
pgcform->relminmxid != minmulti &&
(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
continue;
}
+ /* global temp table relstats not in pg_class */
+ if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+
/*
* Some table AMs might not need per-relation xid / multixid horizons.
* It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
Assert(TransactionIdIsNormal(newFrozenXid));
Assert(MultiXactIdIsValid(newMinMulti));
+ /*
+ * Global temp table get frozenxid from MyProc
+ * to avoid the vacuum truncate clog that gtt need.
+ */
+ if (max_active_gtt > 0)
+ {
+ TransactionId oldest_gtt_frozenxid =
+ list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+ if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+ TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+ {
+ ereport(WARNING,
+ (errmsg("global temp table oldest FrozenXid is far in the past"),
+ errhint("please truncate them or kill those sessions that use them.")));
+ newFrozenXid = oldest_gtt_frozenxid;
+ }
+ }
+
/* Now fetch the pg_database tuple we need to update. */
relation = table_open(DatabaseRelationId, RowExclusiveLock);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ee5c3a6..0d7a2d4 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
if (isTempNamespace(get_rel_namespace(rte->relid)))
continue;
+ if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+
PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
}
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 8286d9c..c3992a4 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte)
{
+ char relpersistence;
+
/*
* The flag has previously been initialized to false, so we can just
* return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* the rest of the necessary infrastructure right now anyway. So
* for now, bail out if we see a temporary table.
*/
- if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+ /* global temp table is same as local temp table */
+ relpersistence = get_rel_persistence(rte->relid);
+
+ if (relpersistence == RELPERSISTENCE_TEMP ||
+ relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
return;
/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d6f2153..310a9e2 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6312,7 +6312,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
* Furthermore, any index predicate or index expressions must be parallel
* safe.
*/
+ /* global temp table is same as local temp table */
if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+ heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
{
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 748bebf..a5ddbcd 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2579,6 +2579,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("materialized views must not use temporary tables or views")));
+ if (is_query_using_gtt(query))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialized views must not use global temporary tables or views")));
+
/*
* A materialized view would either need to save parameters for use in
* maintaining/loading the data or prohibit them entirely. The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1b0edf5..492639f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp: TEMPORARY { $$ = RELPERSISTENCE_TEMP; }
| LOCAL TEMP { $$ = RELPERSISTENCE_TEMP; }
| GLOBAL TEMPORARY
{
- ereport(WARNING,
- (errmsg("GLOBAL is deprecated in temporary table creation"),
- parser_errposition(@1)));
- $$ = RELPERSISTENCE_TEMP;
+ $$ = RELPERSISTENCE_GLOBAL_TEMP;
}
| GLOBAL TEMP
{
- ereport(WARNING,
- (errmsg("GLOBAL is deprecated in temporary table creation"),
- parser_errposition(@1)));
- $$ = RELPERSISTENCE_TEMP;
+ $$ = RELPERSISTENCE_GLOBAL_TEMP;
}
| UNLOGGED { $$ = RELPERSISTENCE_UNLOGGED; }
| /*EMPTY*/ { $$ = RELPERSISTENCE_PERMANENT; }
@@ -11591,19 +11585,13 @@ OptTempTableName:
}
| GLOBAL TEMPORARY opt_table qualified_name
{
- ereport(WARNING,
- (errmsg("GLOBAL is deprecated in temporary table creation"),
- parser_errposition(@1)));
$$ = $4;
- $$->relpersistence = RELPERSISTENCE_TEMP;
+ $$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
}
| GLOBAL TEMP opt_table qualified_name
{
- ereport(WARNING,
- (errmsg("GLOBAL is deprecated in temporary table creation"),
- parser_errposition(@1)));
$$ = $4;
- $$->relpersistence = RELPERSISTENCE_TEMP;
+ $$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
}
| UNLOGGED opt_table qualified_name
{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
List **colnames, List **colvars);
static int specialAttNum(const char *attname);
static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
/*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
isQueryUsingTempRelation_walker,
context);
}
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Query))
+ {
+ Query *query = (Query *) node;
+ ListCell *rtable;
+
+ foreach(rtable, query->rtable)
+ {
+ RangeTblEntry *rte = lfirst(rtable);
+
+ if (rte->rtekind == RTE_RELATION)
+ {
+ Relation rel = relation_open(rte->relid, AccessShareLock);
+ char relpersistence = rel->rd_rel->relpersistence;
+
+ relation_close(rel, AccessShareLock);
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ return true;
+ }
+ }
+
+ return query_tree_walker(query,
+ is_query_using_gtt_walker,
+ context,
+ QTW_IGNORE_JOINALIASES);
+ }
+
+ return expression_tree_walker(node,
+ is_query_using_gtt_walker,
+ context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+ return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee2d2b5..9c9abaa 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
seqstmt->options = seqoptions;
+ if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
/*
* If a sequence data type was specified, add it to the options. Prepend
* to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6d1f28c..ed837d1 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2086,6 +2086,11 @@ do_autovacuum(void)
}
continue;
}
+ else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* autovacuum skip vacuum global temp table */
+ continue;
+ }
/* Fetch reloptions and the pgstat entry for this table */
relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2152,7 +2157,9 @@ do_autovacuum(void)
/*
* We cannot safely process other backends' temp tables, so skip 'em.
*/
- if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+ /* autovacuum skip vacuum global temp table */
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP ||
+ classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
continue;
relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index aba3960..3c4b96c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
#include "executor/instrument.h"
#include "lib/binaryheap.h"
#include "miscadmin.h"
@@ -52,6 +53,7 @@
#include "utils/rel.h"
#include "utils/resowner_private.h"
#include "utils/timestamp.h"
+#include "utils/guc.h"
/* Note: these two macros only work on shared buffers, not local ones! */
@@ -432,7 +434,7 @@ ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref)
static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence,
ForkNumber forkNum, BlockNumber blockNum,
ReadBufferMode mode, BufferAccessStrategy strategy,
- bool *hit);
+ bool *hit, Relation rel);
static bool PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy);
static void PinBuffer_Locked(BufferDesc *buf);
static void UnpinBuffer(BufferDesc *buf, bool fixOwner);
@@ -664,7 +666,8 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
*/
pgstat_count_buffer_read(reln);
buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence,
- forkNum, blockNum, mode, strategy, &hit);
+ forkNum, blockNum, mode, strategy, &hit,
+ reln);
if (hit)
pgstat_count_buffer_hit(reln);
return buf;
@@ -692,7 +695,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
Assert(InRecovery);
return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum,
- mode, strategy, &hit);
+ mode, strategy, &hit, NULL);
}
@@ -704,7 +707,8 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
static Buffer
ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
BlockNumber blockNum, ReadBufferMode mode,
- BufferAccessStrategy strategy, bool *hit)
+ BufferAccessStrategy strategy, bool *hit,
+ Relation rel)
{
BufferDesc *bufHdr;
Block bufBlock;
@@ -719,6 +723,15 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
isExtend = (blockNum == P_NEW);
+ /* create storage when first read data page for gtt */
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+ (isExtend || blockNum == 0) &&
+ forkNum == MAIN_FORKNUM &&
+ !gtt_storage_attached(smgr->smgr_rnode.node.relNode))
+ {
+ RelationCreateStorage(smgr->smgr_rnode.node, relpersistence, rel);
+ }
+
TRACE_POSTGRESQL_BUFFER_READ_START(forkNum, blockNum,
smgr->smgr_rnode.node.spcNode,
smgr->smgr_rnode.node.dbNode,
@@ -2809,6 +2822,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
BlockNumber
RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
{
+ /*
+ * When this backend not init gtt storage
+ * return 0
+ */
+ if (RELATION_IS_GLOBAL_TEMP(relation) &&
+ !gtt_storage_attached(relation->rd_node.relNode))
+ {
+ return 0;
+ }
+
switch (relation->rd_rel->relkind)
{
case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
#include "access/nbtree.h"
#include "access/subtrans.h"
#include "access/twophase.h"
+#include "catalog/storage_gtt.h"
#include "commands/async.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
size = add_size(size, BTreeShmemSize());
size = add_size(size, SyncScanShmemSize());
size = add_size(size, AsyncShmemSize());
+ size = add_size(size, active_gtt_shared_hash_size());
#ifdef EXEC_BACKEND
size = add_size(size, ShmemBackendArraySize());
#endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
SUBTRANSShmemInit();
MultiXactShmemInit();
InitBufferPool();
+ /* global temporary table */
+ active_gtt_shared_hash_init();
/*
* Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index c3adb2e..eb95e5f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
#include "utils/builtins.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
+#include "utils/guc.h"
#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var))))
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
LWLockRelease(ProcArrayLock);
}
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+ ProcArrayStruct *arrayP = procArray;
+ TransactionId result = InvalidTransactionId;
+ int index;
+ int flags = 0;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return 0;
+
+ if (max_size > 0)
+ {
+ Assert(pids);
+ Assert(xids);
+ Assert(n);
+ *n = 0;
+ }
+
+ if (max_active_gtt <= 0)
+ return InvalidTransactionId;
+
+ if (RecoveryInProgress())
+ return InvalidTransactionId;
+
+ flags |= PROC_IS_AUTOVACUUM;
+ flags |= PROC_IN_LOGICAL_DECODING;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+ if (max_size > 0 && max_size < arrayP->numProcs)
+ {
+ LWLockRelease(ProcArrayLock);
+ elog(ERROR, "list_all_gtt_frozenxids require more array");
+ }
+
+ for (index = 0; index < arrayP->numProcs; index++)
+ {
+ int pgprocno = arrayP->pgprocnos[index];
+ volatile PGPROC *proc = &allProcs[pgprocno];
+ volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+ if (pgxact->vacuumFlags & flags)
+ continue;
+
+ if (proc->databaseId == MyDatabaseId &&
+ TransactionIdIsNormal(proc->session_gtt_frozenxid))
+ {
+ if (result == InvalidTransactionId)
+ result = proc->session_gtt_frozenxid;
+ else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+ result = proc->session_gtt_frozenxid;
+
+ if (max_size > 0)
+ {
+ pids[i] = proc->pid;
+ xids[i] = proc->session_gtt_frozenxid;
+ i++;
+ }
+ }
+ }
+ LWLockRelease(ProcArrayLock);
+
+ if (max_size > 0)
+ *n = i;
+
+ return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 32df8c8..e4e1125 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->tempNamespaceId = InvalidOid;
+ MyProc->session_gtt_frozenxid = InvalidTransactionId; /* init session level gtt frozenxid */
MyProc->isBackgroundWorker = IsBackgroundWorker;
MyPgXact->delayChkpt = false;
MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->tempNamespaceId = InvalidOid;
+ MyProc->session_gtt_frozenxid = InvalidTransactionId; /* init session level gtt frozenxid */
MyProc->isBackgroundWorker = IsBackgroundWorker;
MyPgXact->delayChkpt = false;
MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index c5b771c..e7725ce 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -646,6 +646,12 @@ mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
*/
if (zero_damaged_pages || InRecovery)
MemSet(buffer, 0, BLCKSZ);
+ else if(SmgrIsTemp(reln) && blocknum == 0 && forknum == MAIN_FORKNUM)
+ {
+ /* global temp table init btree meta page */
+ MemSet(buffer, 0, BLCKSZ);
+ mdwrite(reln, forknum, blocknum, buffer, true);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
Assert(backend != InvalidBackendId);
}
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ backend = BackendIdForTempRelations();
+ break;
default:
elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 7c6f057..eb10cf2 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
#include "executor/nodeAgg.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -4578,12 +4579,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
}
else if (index->indpred == NIL)
{
- vardata->statsTuple =
- SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(index->indexoid),
- Int16GetDatum(pos + 1),
- BoolGetDatum(false));
- vardata->freefunc = ReleaseSysCache;
+ char rel_persistence = get_rel_persistence(index->indexoid);
+
+ if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata->statsTuple =
+ get_gtt_att_statistic(index->indexoid,
+ Int16GetDatum(pos + 1),
+ false);
+ vardata->freefunc = release_gtt_statistic_cache;
+ }
+ else
+ {
+ vardata->statsTuple =
+ SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(index->indexoid),
+ Int16GetDatum(pos + 1),
+ BoolGetDatum(false));
+ vardata->freefunc = ReleaseSysCache;
+ }
if (HeapTupleIsValid(vardata->statsTuple))
{
@@ -4708,15 +4722,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
}
else if (rte->rtekind == RTE_RELATION)
{
- /*
- * Plain table or parent of an inheritance appendrel, so look up the
- * column in pg_statistic
- */
- vardata->statsTuple = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(rte->relid),
- Int16GetDatum(var->varattno),
- BoolGetDatum(rte->inh));
- vardata->freefunc = ReleaseSysCache;
+ char rel_persistence = get_rel_persistence(rte->relid);
+
+ if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+ var->varattno,
+ rte->inh);
+ vardata->freefunc = release_gtt_statistic_cache;
+ }
+ else
+ {
+ /*
+ * Plain table or parent of an inheritance appendrel, so look up the
+ * column in pg_statistic
+ */
+ vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(rte->relid),
+ Int16GetDatum(var->varattno),
+ BoolGetDatum(rte->inh));
+ vardata->freefunc = ReleaseSysCache;
+ }
if (HeapTupleIsValid(vardata->statsTuple))
{
@@ -6098,6 +6124,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
{
/* Simple variable --- look to stats for the underlying table */
RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+ char rel_persistence = get_rel_persistence(rte->relid);
Assert(rte->rtekind == RTE_RELATION);
relid = rte->relid;
@@ -6115,6 +6142,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata.statsTuple = get_gtt_att_statistic(relid,
+ colnum,
+ rte->inh);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6126,6 +6160,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
}
else
{
+ char rel_persistence = get_rel_persistence(index->indexoid);
+
/* Expression --- maybe there are stats for the index itself */
relid = index->indexoid;
colnum = 1;
@@ -6141,6 +6177,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata.statsTuple = get_gtt_att_statistic(relid,
+ colnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7054,6 +7097,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
/* attempt to lookup stats in relation for this index column */
if (attnum != 0)
{
+ char rel_persistence = get_rel_persistence(rte->relid);
+
/* Simple variable -- look to stats for the underlying table */
if (get_relation_stats_hook &&
(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7066,6 +7111,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
elog(ERROR,
"no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata.statsTuple =
+ get_gtt_att_statistic(rte->relid,
+ attnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple =
@@ -7078,6 +7131,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
}
else
{
+ char rel_persistence = get_rel_persistence(index->indexoid);
+
/*
* Looks like we've found an expression column in the index. Let's
* see if there's any stats for it.
@@ -7097,6 +7152,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata.statsTuple =
+ get_gtt_att_statistic(index->indexoid,
+ attnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1e3e6d3..345e3e9 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
#include "catalog/pg_statistic.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
if (stawidth > 0)
return stawidth;
}
+ if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ tp = get_gtt_att_statistic(relid, attnum, false);
+ if (!HeapTupleIsValid(tp))
+ return 0;
+
+ stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+ if (stawidth > 0)
+ return stawidth;
+ else
+ return 0;
+ }
tp = SearchSysCache3(STATRELATTINH,
ObjectIdGetDatum(relid),
Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index df025a5..8df9c7c 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
#include "catalog/pg_type.h"
#include "catalog/schemapg.h"
#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
#include "commands/policy.h"
#include "commands/trigger.h"
#include "miscadmin.h"
@@ -1124,6 +1125,29 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_islocaltemp = false;
}
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ {
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+
+ relation->rd_backend = BackendIdForTempRelations();
+ /*
+ * For global temp table, all backend can use
+ * this relation, so rd_islocaltemp is true
+ * in every backend.
+ */
+ relation->rd_islocaltemp = true;
+ get_gtt_relstats(RelationGetRelid(relation),
+ &relpages,
+ &reltuples,
+ &relallvisible,
+ NULL, NULL);
+ relation->rd_rel->relpages = (int32)relpages;
+ relation->rd_rel->reltuples = (float4)reltuples;
+ relation->rd_rel->relallvisible = (int32)relallvisible;
+ }
+ break;
default:
elog(ERROR, "invalid relpersistence: %c",
relation->rd_rel->relpersistence);
@@ -1178,6 +1202,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
case RELKIND_PARTITIONED_INDEX:
Assert(relation->rd_rel->relam != InvalidOid);
RelationInitIndexAccessInfo(relation);
+ gtt_fix_index_state(relation);
break;
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
@@ -2217,6 +2242,8 @@ RelationReloadIndexInfo(Relation relation)
HeapTupleHeaderGetXmin(tuple->t_data));
ReleaseSysCache(tuple);
+
+ gtt_fix_index_state(relation);
}
/* Okay, now it's valid again */
@@ -3313,6 +3340,15 @@ RelationBuildLocalRelation(const char *relname,
rel->rd_backend = BackendIdForTempRelations();
rel->rd_islocaltemp = true;
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ rel->rd_backend = BackendIdForTempRelations();
+ /*
+ * For global temp table, all backend can use
+ * this relation, so rd_islocaltemp is true
+ * in every backend.
+ */
+ rel->rd_islocaltemp = true;
+ break;
default:
elog(ERROR, "invalid relpersistence: %c", relpersistence);
break;
@@ -3427,6 +3463,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
TransactionId freezeXid = InvalidTransactionId;
RelFileNode newrnode;
+ if (RELATION_IS_GLOBAL_TEMP(relation))
+ elog(ERROR, "global temp table does not allow setting new relfilenode");
+
/* Allocate a new relfilenode */
newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
persistence);
@@ -3467,7 +3506,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
/* handle these directly, at least for now */
SMgrRelation srel;
- srel = RelationCreateStorage(newrnode, persistence);
+ srel = RelationCreateStorage(newrnode, persistence, relation);
smgrclose(srel);
}
break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index cacbe90..9c4220b 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -140,6 +140,18 @@ char *GUC_check_errmsg_string;
char *GUC_check_errdetail_string;
char *GUC_check_errhint_string;
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define MIN_NUM_ACTIVE_GTT 0
+#define DEFAULT_NUM_ACTIVE_GTT 1000
+#define MAX_NUM_ACTIVE_GTT 1000000
+
+int max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2012,6 +2024,15 @@ static struct config_bool ConfigureNamesBool[] =
static struct config_int ConfigureNamesInt[] =
{
{
+ {"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+ gettext_noop("max active global temporary table."),
+ NULL
+ },
+ &max_active_gtt,
+ DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+ NULL, NULL, NULL
+ },
+ {
{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
gettext_noop("Forces a switch to the next WAL file if a "
"new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ec3e2c6..d3697d2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15583,6 +15583,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
{
char *ftoptions = NULL;
char *srvname = NULL;
+ char *table_type = NULL;
switch (tbinfo->relkind)
{
@@ -15634,9 +15635,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
binary_upgrade_set_pg_class_oids(fout, q,
tbinfo->dobj.catId.oid, false);
+ if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+ table_type = "UNLOGGED ";
+ else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ table_type = "GLOBAL TEMPORARY ";
+ else
+ table_type = "";
+
appendPQExpBuffer(q, "CREATE %s%s %s",
- tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
- "UNLOGGED " : "",
+ table_type,
reltypename,
qualrelname);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
if (pset.sversion >= 90100)
{
appendPQExpBuffer(&buf,
- ",\n CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+ ",\n CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+ gettext_noop("session"),
gettext_noop("permanent"),
gettext_noop("temporary"),
gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index dc03fbd..8bd6d09 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1016,6 +1016,8 @@ static const pgsql_thing_t words_after_create[] = {
{"FOREIGN TABLE", NULL, NULL, NULL},
{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
{"GROUP", Query_for_list_of_roles},
+ {"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+ * ... */
{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
{"LANGUAGE", Query_for_list_of_languages},
{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2378,6 +2380,9 @@ psql_completion(const char *text, int start, int end)
/* CREATE FOREIGN DATA WRAPPER */
else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+ /* CREATE GLOBAL TEMP/TEMPORARY*/
+ else if (Matches("CREATE", "GLOBAL"))
+ COMPLETE_WITH("TEMP", "TEMPORARY");
/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2586,6 +2591,8 @@ psql_completion(const char *text, int start, int end)
/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+ else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+ COMPLETE_WITH("TABLE");
/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
else if (TailMatches("CREATE", "UNLOGGED"))
COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index a12fc1f..78958e4 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -165,6 +165,7 @@ typedef FormData_pg_class *Form_pg_class;
#define RELPERSISTENCE_PERMANENT 'p' /* regular table */
#define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */
#define RELPERSISTENCE_TEMP 't' /* temporary table */
+#define RELPERSISTENCE_GLOBAL_TEMP 'g' /* global temporary table */
/* default selection for replica identity (primary key or nothing) */
#define REPLICA_IDENTITY_DEFAULT 'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2228256..4d5d13a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5504,6 +5504,40 @@
proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
prosrc => 'pg_stat_get_xact_function_self_time' },
+# For global temporary table
+{ oid => '4191',
+ descr => 'List local statistics for global temporary table',
+ proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+ proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+ proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+ proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+ prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+ descr => 'List local relstats for global temporary table',
+ proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+ proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+ proargmodes => '{i,o,o,o,o,o}',
+ proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+ prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+ descr => 'List attached pid for one global temporary table',
+ proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+ proallargtypes => '{oid,oid,int4}',
+ proargmodes => '{i,o,o}',
+ proargnames => '{relid,relid,pid}',
+ prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+ descr => 'List those backends that have used global temporary table',
+ proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+ proallargtypes => '{int4,xid}',
+ proargmodes => '{o,o}',
+ proargnames => '{pid,relfrozenxid}',
+ prosrc => 'pg_list_gtt_relfrozenxids' },
+
{ oid => '3788',
descr => 'statistics: timestamp of the current statistics snapshot',
proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
#include "storage/smgr.h"
#include "utils/relcache.h"
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
extern void RelationDropStorage(Relation rel);
extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..aa80cb5
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,41 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ * prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+
+#endif /* STORAGE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid attnumTypeId(Relation rd, int attid);
extern Oid attnumCollationId(Relation rd, int attid);
extern bool isQueryUsingTempRelation(Query *query);
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
#endif /* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 870ecb5..92c590e 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
#define PageClearPrunable(page) \
(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+ ((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
/* ----------------------------------------------------------------
* extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_TBM,
LWTRANCHE_PARALLEL_APPEND,
LWTRANCHE_SXACT,
+ LWTRANCHE_GTT_CTL,
LWTRANCHE_FIRST_USER_DEFINED
} BuiltinTrancheIds;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
Oid tempNamespaceId; /* OID of temp schema this backend is
* using */
+ TransactionId session_gtt_frozenxid; /* session level global temp table relfrozenxid */
+
bool isBackgroundWorker; /* true if background worker. */
/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
TransactionId *catalog_xmin);
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
#endif /* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int tcp_user_timeout;
extern bool trace_sort;
#endif
+/* global temporary table */
+extern int max_active_gtt;
+/* end */
+
/*
* Functions exported by guc.c
*/
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..b308cb8 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
struct SMgrRelationData *rd_smgr; /* cached file handle, or NULL */
int rd_refcnt; /* reference count */
BackendId rd_backend; /* owning backend id, if temporary relation */
- bool rd_islocaltemp; /* rel is a temp rel of this session */
+ bool rd_islocaltemp; /* rel is a temp rel of this session */
bool rd_isnailed; /* rel is nailed in cache */
bool rd_isvalid; /* relcache entry is valid */
bool rd_indexvalid; /* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
int parallel_workers; /* max number of parallel workers */
bool vacuum_index_cleanup; /* enables index vacuuming and cleanup */
bool vacuum_truncate; /* enables vacuum to truncate a relation */
+ bool on_commit_delete_rows; /* global temp table */
} StdRdOptions;
#define HEAP_MIN_FILLFACTOR 10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
* True if relation's pages are stored in local buffers.
*/
#define RelationUsesLocalBuffers(relation) \
- ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
/*
* RELATION_IS_LOCAL
- * If a rel is either temp or newly created in the current transaction,
+ * If a rel is either local temp or global temp relation
+ * or newly created in the current transaction,
* it can be assumed to be accessible only to the current backend.
* This is typically used to decide that we can skip acquiring locks.
*
@@ -602,6 +605,17 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation) ((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation) \
+ ((relation)->rd_options && \
+ ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+ ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..e21e540
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,282 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+ product_no integer PRIMARY KEY,
+ name text,
+ price numeric
+);
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR: cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR: not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR: ON COMMIT can only be used on temporary tables
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR: regular table cannot specifie on_commit_delete_rows
+-- ok
+CREATE global temp TABLE measurement (
+ logdate date not null,
+ peaktemp int,
+ unitsales int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count
+-------
+ 2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count
+-------
+ 0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR: The parent table must be global temporary table
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname;
+ relname | relkind | relpersistence | reloptions
+---------------------------------+---------+----------------+-------------------------------
+ p_table01 | p | g | {on_commit_delete_rows=true}
+ p_table01_2017 | r | g | {on_commit_delete_rows=true}
+ p_table01_2018 | r | g | {on_commit_delete_rows=true}
+ p_table01_id_seq | S | g |
+ p_table02 | p | g | {on_commit_delete_rows=false}
+ p_table02_2017 | r | g | {on_commit_delete_rows=false}
+ p_table02_2018 | r | g | {on_commit_delete_rows=false}
+ p_table02_id_seq | S | g |
+ tbl_inherits_parent | r | p |
+ tbl_inherits_parent_global_temp | r | g | {on_commit_delete_rows=true}
+ tbl_inherits_partition | r | g | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR: global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR: can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+ERROR: referenced relation "products" is not a global temp table
+-- ERROR
+CREATE global temp TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+ERROR: referenced relation "products" is not a global temp table
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+ id SERIAL PRIMARY KEY,
+ data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a
+----+---
+ 2 | 1
+ 3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a
+----+---
+ 4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR: materialized views must not use global temporary tables or views
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+ERROR: only support btree index on global temp table
+create index idx_err on gtt1 using gist (a);
+ERROR: data type integer has no default operator class for access method "gist"
+HINT: You must specify an operator class for the index or define a default operator class for the data type.
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+ERROR: only support btree index on global temp table
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+ERROR: only support btree index on global temp table
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain select * from gt1 where a=1;
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on gt1 (cost=12.17..482.50 rows=500 width=4)
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on idx_gt1_1 (cost=0.00..12.04 rows=500 width=0)
+ Index Cond: (a = 1)
+(4 rows)
+
+explain select * from gt1 where a=200000;
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on gt1 (cost=12.17..482.50 rows=500 width=4)
+ Recheck Cond: (a = 200000)
+ -> Bitmap Index Scan on idx_gt1_1 (cost=0.00..12.04 rows=500 width=0)
+ Index Cond: (a = 200000)
+(4 rows)
+
+explain select * from gt1 where a*10=300;
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on gt1 (cost=12.29..483.88 rows=500 width=4)
+ Recheck Cond: ((a * 10) = 300)
+ -> Bitmap Index Scan on idx_gt1_3 (cost=0.00..12.17 rows=500 width=0)
+ Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain select * from gt1 where a*10=3;
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on gt1 (cost=12.29..483.88 rows=500 width=4)
+ Recheck Cond: ((a * 10) = 3)
+ -> Bitmap Index Scan on idx_gt1_3 (cost=0.00..12.17 rows=500 width=0)
+ Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain select * from gt1 where a=1;
+ QUERY PLAN
+--------------------------------------------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1 (cost=0.29..8.31 rows=1 width=4)
+ Index Cond: (a = 1)
+(2 rows)
+
+explain select * from gt1 where a=200000;
+ QUERY PLAN
+--------------------------------------------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1 (cost=0.29..8.31 rows=1 width=4)
+ Index Cond: (a = 200000)
+(2 rows)
+
+explain select * from gt1 where a*10=300;
+ QUERY PLAN
+---------------------------------------------------------------------
+ Index Scan using idx_gt1_3 on gt1 (cost=0.42..8.44 rows=1 width=4)
+ Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain select * from gt1 where a*10=3;
+ QUERY PLAN
+---------------------------------------------------------------------
+ Index Scan using idx_gt1_3 on gt1 (cost=0.42..8.44 rows=1 width=4)
+ Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE: drop cascades to 17 other objects
+DETAIL: drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..30d8a7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,84 @@
+set search_path=gtt,sys;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..850ef3e
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,161 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+ QUERY PLAN
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+ Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+ relname | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon | 450560 | 8192 | 499712 | 475136 | 974848
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+ relname
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+ relname | pg_relation_size | pg_table_size | pg_total_relation_size
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 | 65536 | 65536 | 65536
+ idx_gtt_t_kenyon_2 | 409600 | 409600 | 409600
+(2 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..9fe5fd4
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,9 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..b258b7c
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 0
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible
+------------+-----------+----------+-----------+---------------
+ gtt_stats | gtt | 0 | 0 | 0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible
+------------+-----------+----------+-----------+---------------
+ gtt_stats | gtt | 55 | 10000 | 0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats | gtt | a | f | 0 | 4 | -1 | | | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} | 1 | | |
+ gtt_stats | gtt | b | f | 0 | 5 | 1 | {test} | {1} | | 1 | | |
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE: drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2ab2115..4f4eb46 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
FROM pg_authid
WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.relid,
+ s.pid
+ FROM (pg_class c
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.relpages,
+ s.reltuples,
+ s.relallvisible,
+ s.relfrozenxid,
+ s.relminmxid
+ FROM (pg_class c
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN (s.stakind1 = 1) THEN s.stavalues1
+ WHEN (s.stakind2 = 1) THEN s.stavalues2
+ WHEN (s.stakind3 = 1) THEN s.stavalues3
+ WHEN (s.stakind4 = 1) THEN s.stavalues4
+ WHEN (s.stakind5 = 1) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS most_common_vals,
+ CASE
+ WHEN (s.stakind1 = 1) THEN s.stanumbers1
+ WHEN (s.stakind2 = 1) THEN s.stanumbers2
+ WHEN (s.stakind3 = 1) THEN s.stanumbers3
+ WHEN (s.stakind4 = 1) THEN s.stanumbers4
+ WHEN (s.stakind5 = 1) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS most_common_freqs,
+ CASE
+ WHEN (s.stakind1 = 2) THEN s.stavalues1
+ WHEN (s.stakind2 = 2) THEN s.stavalues2
+ WHEN (s.stakind3 = 2) THEN s.stavalues3
+ WHEN (s.stakind4 = 2) THEN s.stavalues4
+ WHEN (s.stakind5 = 2) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS histogram_bounds,
+ CASE
+ WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+ WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+ WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+ WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+ WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+ ELSE NULL::real
+ END AS correlation,
+ CASE
+ WHEN (s.stakind1 = 4) THEN s.stavalues1
+ WHEN (s.stakind2 = 4) THEN s.stavalues2
+ WHEN (s.stakind3 = 4) THEN s.stavalues3
+ WHEN (s.stakind4 = 4) THEN s.stavalues4
+ WHEN (s.stakind5 = 4) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS most_common_elems,
+ CASE
+ WHEN (s.stakind1 = 4) THEN s.stanumbers1
+ WHEN (s.stakind2 = 4) THEN s.stanumbers2
+ WHEN (s.stakind3 = 4) THEN s.stanumbers3
+ WHEN (s.stakind4 = 4) THEN s.stanumbers4
+ WHEN (s.stakind5 = 4) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN (s.stakind1 = 5) THEN s.stanumbers1
+ WHEN (s.stakind2 = 5) THEN s.stanumbers2
+ WHEN (s.stakind3 = 5) THEN s.stanumbers3
+ WHEN (s.stakind4 = 5) THEN s.stanumbers4
+ WHEN (s.stakind5 = 5) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS elem_count_histogram
+ FROM ((pg_class c
+ JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
pg_hba_file_rules| SELECT a.line_number,
a.type,
a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
# run stats by itself because its delay may be insufficient under heavy load
test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..784c537
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,187 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+ product_no integer PRIMARY KEY,
+ name text,
+ price numeric
+);
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+ logdate date not null,
+ peaktemp int,
+ unitsales int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+
+-- ERROR
+CREATE global temp TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+ id SERIAL PRIMARY KEY,
+ data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+create index idx_err on gtt1 using gist (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain select * from gt1 where a=1;
+explain select * from gt1 where a=200000;
+explain select * from gt1 where a*10=300;
+explain select * from gt1 where a*10=3;
+analyze gt1;
+explain select * from gt1 where a=1;
+explain select * from gt1 where a=200000;
+explain select * from gt1 where a*10=300;
+explain select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..5203c2b
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,76 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..2f4d883
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,16 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
č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 nameof 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
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
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_islocaltempI 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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+ int natts = 0;
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temp table yet");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create stroage file", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temp relation table",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ elog(ERROR, "backend %d relid %u already exists in global temp table local hash",
+ MyBackendId, relid);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry->spcnode = rnode.spcNode;
+ entry->relpages = 0;
+ entry->reltuples = 0;
+ entry->relallvisible = 0;
+ entry->relkind = rel->rd_rel->relkind;
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->natts = natts;
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+ }
+
+ entry->relfrozenxid = RecentXmin;
+ entry->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ gtt_storage_checkin(rnode);
+ }
+ else
+ {
+ entry->on_commit_delete = false;
+ entry->relfrozenxid = 0;
+ entry->relminmxid = 0;
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid)
+{
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, NULL);
+
+ if (entry)
+ {
+ int i;
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ RelFileNode rnode;
+
+ rnode.spcNode = entry->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = entry->relid;
+
+ gtt_storage_checkout(rnode, false);
+
+ Assert(TransactionIdIsNormal(entry->relfrozenxid));
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->att_stat_tups[i])
+ {
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ }
+ }
+
+ pfree(entry->attnum);
+ pfree(entry->att_stat_tups);
+ }
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, &found);
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ int nrels = 0,
+ maxrels = 0;
+ SMgrRelation *srels = NULL;
+ RelFileNode *rnodes = NULL;
+ char *relkinds = NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+
+ rnode.spcNode = entry->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = entry->relid;
+
+ srel = smgropen(rnode, MyBackendId);
+
+ /* allocate the initial array, or extend it, if needed */
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxrels);
+ rnodes = palloc(sizeof(RelFileNode) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+ rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ srels[nrels] = srel;
+ rnodes[nrels] = rnode;
+ relkinds[nrels] = entry->relkind;
+ nrels++;
+ }
+
+ if (nrels > 0)
+ {
+ int i;
+
+ smgrdounlinkall(srels, nrels, false);
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ /* todo */
+ if (entry->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ Assert(entry->relid == reloid);
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == 0)
+ {
+ entry->attnum[i] = attnum;
+ break;
+ }
+ else if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < entry->natts);
+ Assert(entry->att_stat_tups[i] == NULL);
+ entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return NULL;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ return entry->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+ MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ HeapTuple tuple;
+ Relation rel = NULL;
+ int attnum = PG_GETARG_INT32(1);
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Tuplestorestate *tupstore;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ tuple = get_gtt_att_statistic(reloid, attnum, false);
+ if (tuple)
+ {
+ Datum values[31];
+ bool isnull[31];
+ HeapTuple res = NULL;
+
+ memset(&values, 0, sizeof(values));
+ memset(&isnull, 0, sizeof(isnull));
+ heap_deform_tuple(tuple, sd, values, isnull);
+ res = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, res);
+ }
+
+ table_close(rel, NoLock);
+ table_close(pg_tatistic, AccessShareLock);
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ if (get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid))
+ {
+ Datum values[5];
+ bool isnull[5];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(relpages);
+ values[1] = Float4GetDatum((float4)reltuples);
+ values[2] = Int32GetDatum(relallvisible);
+ values[3] = UInt32GetDatum(relfrozenxid);
+ values[4] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ map = copy_active_gtt_bitmap(rel->rd_node);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ int i = 0;
+ int j = 0;
+ uint32 oldest = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ if (max_active_gtt <= 0)
+ return (Datum) 0;
+
+ if (RecoveryInProgress())
+ return (Datum) 0;
+
+ pids = palloc0(sizeof(int) * num_xid);
+ xids = palloc0(sizeof(int) * num_xid);
+ oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+ if (i > 0)
+ {
+ if (i > 0)
+ {
+ pids[i] = 0;
+ xids[i] = oldest;
+ i++;
+ }
+
+ for(j = 0; j < i; j++)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(pids[j]);
+ values[1] = UInt32GetDatum(xids[j]);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ }
+ tuplestore_donestoring(tupstore);
+ pfree(pids);
+ pfree(xids);
+
+ return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(indexOid));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid relOid = index->rd_index->indrelid;
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ if (gtt_storage_attached(relOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c9e6060..d814ff2 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
WHERE NOT pg_is_other_temp_schema(N.oid)
AND relkind = 'S';
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stavalues1
+ WHEN s.stakind2 = 1 THEN s.stavalues2
+ WHEN s.stakind3 = 1 THEN s.stavalues3
+ WHEN s.stakind4 = 1 THEN s.stavalues4
+ WHEN s.stakind5 = 1 THEN s.stavalues5
+ END AS most_common_vals,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stanumbers1
+ WHEN s.stakind2 = 1 THEN s.stanumbers2
+ WHEN s.stakind3 = 1 THEN s.stanumbers3
+ WHEN s.stakind4 = 1 THEN s.stanumbers4
+ WHEN s.stakind5 = 1 THEN s.stanumbers5
+ END AS most_common_freqs,
+ CASE
+ WHEN s.stakind1 = 2 THEN s.stavalues1
+ WHEN s.stakind2 = 2 THEN s.stavalues2
+ WHEN s.stakind3 = 2 THEN s.stavalues3
+ WHEN s.stakind4 = 2 THEN s.stavalues4
+ WHEN s.stakind5 = 2 THEN s.stavalues5
+ END AS histogram_bounds,
+ CASE
+ WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+ WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+ WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+ WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+ WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+ END AS correlation,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stavalues1
+ WHEN s.stakind2 = 4 THEN s.stavalues2
+ WHEN s.stakind3 = 4 THEN s.stavalues3
+ WHEN s.stakind4 = 4 THEN s.stavalues4
+ WHEN s.stakind5 = 4 THEN s.stavalues5
+ END AS most_common_elems,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stanumbers1
+ WHEN s.stakind2 = 4 THEN s.stanumbers2
+ WHEN s.stakind3 = 4 THEN s.stanumbers3
+ WHEN s.stakind4 = 4 THEN s.stanumbers4
+ WHEN s.stakind5 = 4 THEN s.stanumbers5
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN s.stakind1 = 5 THEN s.stanumbers1
+ WHEN s.stakind2 = 5 THEN s.stanumbers2
+ WHEN s.stakind3 = 5 THEN s.stanumbers3
+ WHEN s.stakind4 = 5 THEN s.stanumbers4
+ WHEN s.stakind5 = 5 THEN s.stanumbers5
+ END AS elem_count_histogram
+ FROM
+ pg_class c
+ JOIN pg_attribute a ON c.oid = a.attrelid
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
CREATE VIEW pg_stats WITH (security_barrier) AS
SELECT
nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..977a984 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
#include "commands/dbcommands.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int acquire_inherited_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
static void update_attstats(Oid relid, bool inh,
- int natts, VacAttrStats **vacattrstats);
+ int natts, VacAttrStats **vacattrstats, char relpersistence);
static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
@@ -586,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* pg_statistic for columns we didn't process, we leave them alone.)
*/
update_attstats(RelationGetRelid(onerel), inh,
- attr_cnt, vacattrstats);
+ attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
for (ind = 0; ind < nindexes; ind++)
{
AnlIndexData *thisdata = &indexdata[ind];
update_attstats(RelationGetRelid(Irel[ind]), false,
- thisdata->attr_cnt, thisdata->vacattrstats);
+ thisdata->attr_cnt, thisdata->vacattrstats,
+ RelationGetRelPersistence(Irel[ind]));
}
/*
@@ -1456,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
* by taking a self-exclusive lock on the relation in analyze_rel().
*/
static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
{
Relation sd;
int attno;
@@ -1558,31 +1560,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
}
}
- /* Is there already a pg_statistic tuple for this attribute? */
- oldtup = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(relid),
- Int16GetDatum(stats->attr->attnum),
- BoolGetDatum(inh));
-
- if (HeapTupleIsValid(oldtup))
+ /* Update column statistic to localhash, not catalog */
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
{
- /* Yes, replace it */
- stup = heap_modify_tuple(oldtup,
- RelationGetDescr(sd),
- values,
- nulls,
- replaces);
- ReleaseSysCache(oldtup);
- CatalogTupleUpdate(sd, &stup->t_self, stup);
+ up_gtt_att_statistic(relid,
+ stats->attr->attnum,
+ inh,
+ natts,
+ RelationGetDescr(sd),
+ values,
+ nulls);
}
else
{
- /* No, insert new tuple */
- stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
- CatalogTupleInsert(sd, stup);
- }
+ /* Is there already a pg_statistic tuple for this attribute? */
+ oldtup = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(relid),
+ Int16GetDatum(stats->attr->attnum),
+ BoolGetDatum(inh));
- heap_freetuple(stup);
+ if (HeapTupleIsValid(oldtup))
+ {
+ /* Yes, replace it */
+ stup = heap_modify_tuple(oldtup,
+ RelationGetDescr(sd),
+ values,
+ nulls,
+ replaces);
+ ReleaseSysCache(oldtup);
+ CatalogTupleUpdate(sd, &stup->t_self, stup);
+ }
+ else
+ {
+ /* No, insert new tuple */
+ stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+ CatalogTupleInsert(sd, stup);
+ }
+
+ heap_freetuple(stup);
+ }
}
table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index e9d7a7f..3088e26 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
errmsg("cannot vacuum temporary tables of other sessions")));
}
+ /* not support cluster global temp table yet */
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("not support cluster global temporary tables yet")));
+
/*
* Also check for active uses of the relation in the current transaction,
* including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/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;
+
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/>
The Russian Postgres Company
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 thename 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
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
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
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_islocaltempI 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/>
The Enterprise PostgreSQL Company
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 thename 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
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>
Show quoted text
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com <http://www.postgrespro.com/>
The Russian Postgres Company
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/>|)
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>
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.
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
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
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.
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
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 sessionsYes, 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/>) 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>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.
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
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
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.
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.
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
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
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
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
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 100000Session2:
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 INDEXSessin2:
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
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 INDEXSessin2:
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).
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 INDEXSessin2:
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
č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 thename 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
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_islocaltempI 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/>
The Enterprise PostgreSQL Company
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
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 INDEXSessin2:
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+ int natts = 0;
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temp table yet");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ /* todo */
+ if (entry->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ Assert(entry->relid == reloid);
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == 0)
+ {
+ entry->attnum[i] = attnum;
+ break;
+ }
+ else if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < entry->natts);
+ Assert(entry->att_stat_tups[i] == NULL);
+ entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return NULL;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ return entry->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+ MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ HeapTuple tuple;
+ Relation rel = NULL;
+ int attnum = PG_GETARG_INT32(1);
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Tuplestorestate *tupstore;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ tuple = get_gtt_att_statistic(reloid, attnum, false);
+ if (tuple)
+ {
+ Datum values[31];
+ bool isnull[31];
+ HeapTuple res = NULL;
+
+ memset(&values, 0, sizeof(values));
+ memset(&isnull, 0, sizeof(isnull));
+ heap_deform_tuple(tuple, sd, values, isnull);
+ res = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, res);
+ }
+
+ table_close(rel, NoLock);
+ table_close(pg_tatistic, AccessShareLock);
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ if (get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid))
+ {
+ Datum values[5];
+ bool isnull[5];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(relpages);
+ values[1] = Float4GetDatum((float4)reltuples);
+ values[2] = Int32GetDatum(relallvisible);
+ values[3] = UInt32GetDatum(relfrozenxid);
+ values[4] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ map = copy_active_gtt_bitmap(rel->rd_node);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ int i = 0;
+ int j = 0;
+ uint32 oldest = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ if (max_active_gtt <= 0)
+ return (Datum) 0;
+
+ if (RecoveryInProgress())
+ return (Datum) 0;
+
+ pids = palloc0(sizeof(int) * num_xid);
+ xids = palloc0(sizeof(int) * num_xid);
+ oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+ if (i > 0)
+ {
+ if (i > 0)
+ {
+ pids[i] = 0;
+ xids[i] = oldest;
+ i++;
+ }
+
+ for(j = 0; j < i; j++)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(pids[j]);
+ values[1] = UInt32GetDatum(xids[j]);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ }
+ tuplestore_donestoring(tupstore);
+ pfree(pids);
+ pfree(xids);
+
+ return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(indexOid));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid relOid = index->rd_index->indrelid;
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ if (gtt_storage_attached(relOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
+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;
+
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/>
The Enterprise PostgreSQL Company
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
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
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/>
The Enterprise PostgreSQL Company
--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+ int natts = 0;
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temp table yet");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ /* todo */
+ if (entry->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ Assert(entry->relid == reloid);
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == 0)
+ {
+ entry->attnum[i] = attnum;
+ break;
+ }
+ else if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < entry->natts);
+ Assert(entry->att_stat_tups[i] == NULL);
+ entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return NULL;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ return entry->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+ MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ HeapTuple tuple;
+ Relation rel = NULL;
+ int attnum = PG_GETARG_INT32(1);
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Tuplestorestate *tupstore;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ tuple = get_gtt_att_statistic(reloid, attnum, false);
+ if (tuple)
+ {
+ Datum values[31];
+ bool isnull[31];
+ HeapTuple res = NULL;
+
+ memset(&values, 0, sizeof(values));
+ memset(&isnull, 0, sizeof(isnull));
+ heap_deform_tuple(tuple, sd, values, isnull);
+ res = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, res);
+ }
+
+ table_close(rel, NoLock);
+ table_close(pg_tatistic, AccessShareLock);
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ if (get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid))
+ {
+ Datum values[5];
+ bool isnull[5];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(relpages);
+ values[1] = Float4GetDatum((float4)reltuples);
+ values[2] = Int32GetDatum(relallvisible);
+ values[3] = UInt32GetDatum(relfrozenxid);
+ values[4] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ map = copy_active_gtt_bitmap(rel->rd_node);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ int i = 0;
+ int j = 0;
+ uint32 oldest = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ if (max_active_gtt <= 0)
+ return (Datum) 0;
+
+ if (RecoveryInProgress())
+ return (Datum) 0;
+
+ pids = palloc0(sizeof(int) * num_xid);
+ xids = palloc0(sizeof(int) * num_xid);
+ oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+ if (i > 0)
+ {
+ if (i > 0)
+ {
+ pids[i] = 0;
+ xids[i] = oldest;
+ i++;
+ }
+
+ for(j = 0; j < i; j++)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(pids[j]);
+ values[1] = UInt32GetDatum(xids[j]);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ }
+ tuplestore_donestoring(tupstore);
+ pfree(pids);
+ pfree(xids);
+
+ return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(indexOid));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid relOid = index->rd_index->indrelid;
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ if (gtt_storage_attached(relOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
+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;
+
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
directoryis 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
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
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
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
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+ int natts = 0;
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temp table yet");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ /* todo */
+ if (entry->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ Assert(entry->relid == reloid);
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == 0)
+ {
+ entry->attnum[i] = attnum;
+ break;
+ }
+ else if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < entry->natts);
+ Assert(entry->att_stat_tups[i] == NULL);
+ entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return NULL;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ return entry->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+ MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ HeapTuple tuple;
+ Relation rel = NULL;
+ int attnum = PG_GETARG_INT32(1);
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Tuplestorestate *tupstore;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ tuple = get_gtt_att_statistic(reloid, attnum, false);
+ if (tuple)
+ {
+ Datum values[31];
+ bool isnull[31];
+ HeapTuple res = NULL;
+
+ memset(&values, 0, sizeof(values));
+ memset(&isnull, 0, sizeof(isnull));
+ heap_deform_tuple(tuple, sd, values, isnull);
+ res = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, res);
+ }
+
+ table_close(rel, NoLock);
+ table_close(pg_tatistic, AccessShareLock);
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ if (get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid))
+ {
+ Datum values[5];
+ bool isnull[5];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(relpages);
+ values[1] = Float4GetDatum((float4)reltuples);
+ values[2] = Int32GetDatum(relallvisible);
+ values[3] = UInt32GetDatum(relfrozenxid);
+ values[4] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ map = copy_active_gtt_bitmap(rel->rd_node);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ int i = 0;
+ int j = 0;
+ uint32 oldest = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ if (max_active_gtt <= 0)
+ return (Datum) 0;
+
+ if (RecoveryInProgress())
+ return (Datum) 0;
+
+ pids = palloc0(sizeof(int) * num_xid);
+ xids = palloc0(sizeof(int) * num_xid);
+ oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+ if (i > 0)
+ {
+ if (i > 0)
+ {
+ pids[i] = 0;
+ xids[i] = oldest;
+ i++;
+ }
+
+ for(j = 0; j < i; j++)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(pids[j]);
+ values[1] = UInt32GetDatum(xids[j]);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ }
+ tuplestore_donestoring(tupstore);
+ pfree(pids);
+ pfree(xids);
+
+ return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(indexOid));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid relOid = index->rd_index->indrelid;
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ if (gtt_storage_attached(relOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
+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;
+
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 directoryis 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 TABLEpostgres=# create global temporary table gtt2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp tablepostgres=# create table tab2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp tableThanks,
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+ int natts = 0;
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temp table yet");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ /* todo */
+ if (entry->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ Assert(entry->relid == reloid);
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == 0)
+ {
+ entry->attnum[i] = attnum;
+ break;
+ }
+ else if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < entry->natts);
+ Assert(entry->att_stat_tups[i] == NULL);
+ entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return NULL;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ return entry->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+ MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ HeapTuple tuple;
+ Relation rel = NULL;
+ int attnum = PG_GETARG_INT32(1);
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Tuplestorestate *tupstore;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ tuple = get_gtt_att_statistic(reloid, attnum, false);
+ if (tuple)
+ {
+ Datum values[31];
+ bool isnull[31];
+ HeapTuple res = NULL;
+
+ memset(&values, 0, sizeof(values));
+ memset(&isnull, 0, sizeof(isnull));
+ heap_deform_tuple(tuple, sd, values, isnull);
+ res = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, res);
+ }
+
+ table_close(rel, NoLock);
+ table_close(pg_tatistic, AccessShareLock);
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ if (get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid))
+ {
+ Datum values[5];
+ bool isnull[5];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(relpages);
+ values[1] = Float4GetDatum((float4)reltuples);
+ values[2] = Int32GetDatum(relallvisible);
+ values[3] = UInt32GetDatum(relfrozenxid);
+ values[4] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ map = copy_active_gtt_bitmap(rel->rd_node);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ int i = 0;
+ int j = 0;
+ uint32 oldest = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ if (max_active_gtt <= 0)
+ return (Datum) 0;
+
+ if (RecoveryInProgress())
+ return (Datum) 0;
+
+ pids = palloc0(sizeof(int) * num_xid);
+ xids = palloc0(sizeof(int) * num_xid);
+ oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+ if (i > 0)
+ {
+ if (i > 0)
+ {
+ pids[i] = 0;
+ xids[i] = oldest;
+ i++;
+ }
+
+ for(j = 0; j < i; j++)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(pids[j]);
+ values[1] = UInt32GetDatum(xids[j]);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ }
+ tuplestore_donestoring(tupstore);
+ pfree(pids);
+ pfree(xids);
+
+ return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(indexOid));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid relOid = index->rd_index->indrelid;
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ if (gtt_storage_attached(relOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
+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;
+
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
directoryis 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 TABLEpostgres=# create global temporary table gtt2 (c1 int references gtt1(c1)
);
ERROR: referenced relation "gtt1" is not a global temp tablepostgres=# create table tab2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp tableThanks,
Prabhat SahuGTT supports foreign key constraints
in global_temporary_table_v13-pg13.patchWenjing
--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com
ú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 TABLEpostgres=# 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 TABLEpostgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
ERROR: constraints on temporary tables may reference only temporary tablespostgres=# create global temporary table gtt2 (c1 int references tmp1(c1)
);
ERROR: constraints on temporary tables may reference only temporary tablesThanks,
Prabhat SahuOn 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
directoryis 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 TABLEpostgres=# create global temporary table gtt2 (c1 int references gtt1(c1)
);
ERROR: referenced relation "gtt1" is not a global temp tablepostgres=# create table tab2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp tableThanks,
Prabhat SahuGTT supports foreign key constraints
in global_temporary_table_v13-pg13.patchWenjing
--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.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
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 TABLEpostgres=# 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 TABLEpostgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
ERROR: constraints on temporary tables may reference only temporary
tablespostgres=# create global temporary table gtt2 (c1 int references
tmp1(c1) );
ERROR: constraints on temporary tables may reference only temporary
tablesThanks,
Prabhat SahuOn 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 directoryis 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 TABLEpostgres=# create global temporary table gtt2 (c1 int references
gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp tablepostgres=# create table tab2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp tableThanks,
Prabhat SahuGTT supports foreign key constraints
in global_temporary_table_v13-pg13.patchWenjing
--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/>
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
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 TABLEpostgres=# 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 TABLEpostgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
ERROR: constraints on temporary tables may reference only
temporary tablespostgres=# create global temporary table gtt2 (c1 int references
tmp1(c1) );
ERROR: constraints on temporary tables may reference only
temporary tablesThanks,
Prabhat SahuOn 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 directoryis 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 TABLEpostgres=# create global temporary table gtt2 (c1 int
references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp tablepostgres=# create table tab2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp tableThanks,
Prabhat SahuGTT supports foreign key constraints
in global_temporary_table_v13-pg13.patchWenjing
--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com
<http://www.enterprisedb.com/>
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
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 TABLEpostgres=# 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 TABLEpostgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
ERROR: constraints on temporary tables may reference only temporary tablespostgres=# create global temporary table gtt2 (c1 int references tmp1(c1) );
ERROR: constraints on temporary tables may reference only temporary tablesThanks,
Prabhat SahuOn 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 directoryis 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 TABLEpostgres=# create global temporary table gtt2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp tablepostgres=# create table tab2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp tableThanks,
Prabhat SahuGTT 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/>--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/ <https://www.enterprisedb.com/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+ int natts = 0;
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temp table yet");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ /* todo */
+ if (entry->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ Assert(entry->relid == reloid);
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == 0)
+ {
+ entry->attnum[i] = attnum;
+ break;
+ }
+ else if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < entry->natts);
+ Assert(entry->att_stat_tups[i] == NULL);
+ entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return NULL;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ return entry->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+ MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ HeapTuple tuple;
+ Relation rel = NULL;
+ int attnum = PG_GETARG_INT32(1);
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Tuplestorestate *tupstore;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ tuple = get_gtt_att_statistic(reloid, attnum, false);
+ if (tuple)
+ {
+ Datum values[31];
+ bool isnull[31];
+ HeapTuple res = NULL;
+
+ memset(&values, 0, sizeof(values));
+ memset(&isnull, 0, sizeof(isnull));
+ heap_deform_tuple(tuple, sd, values, isnull);
+ res = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, res);
+ }
+
+ table_close(rel, NoLock);
+ table_close(pg_tatistic, AccessShareLock);
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ if (get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid))
+ {
+ Datum values[5];
+ bool isnull[5];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(relpages);
+ values[1] = Float4GetDatum((float4)reltuples);
+ values[2] = Int32GetDatum(relallvisible);
+ values[3] = UInt32GetDatum(relfrozenxid);
+ values[4] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ map = copy_active_gtt_bitmap(rel->rd_node);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ int i = 0;
+ int j = 0;
+ uint32 oldest = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ if (max_active_gtt <= 0)
+ return (Datum) 0;
+
+ if (RecoveryInProgress())
+ return (Datum) 0;
+
+ pids = palloc0(sizeof(int) * num_xid);
+ xids = palloc0(sizeof(int) * num_xid);
+ oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+ if (i > 0)
+ {
+ if (i > 0)
+ {
+ pids[i] = 0;
+ xids[i] = oldest;
+ i++;
+ }
+
+ for(j = 0; j < i; j++)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(pids[j]);
+ values[1] = UInt32GetDatum(xids[j]);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ }
+ tuplestore_donestoring(tupstore);
+ pfree(pids);
+ pfree(xids);
+
+ return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(indexOid));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid relOid = index->rd_index->indrelid;
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ if (gtt_storage_attached(relOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
+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;
+
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 TABLECase 2-
postgres=# create global temp table bar1(n int) on commit delete rows;
CREATE TABLEbut 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 TABLEi think this error message need to be more clear .
Also fixed in global_temporary_table_v14-pg13.patch
Wenjing
Show quoted text
regards,
tusharOn 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 TABLEpostgres=# 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 TABLEpostgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
ERROR: constraints on temporary tables may reference only temporary tablespostgres=# create global temporary table gtt2 (c1 int references tmp1(c1) );
ERROR: constraints on temporary tables may reference only temporary tablesThanks,
Prabhat SahuOn 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 directoryis 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 TABLEpostgres=# create global temporary table gtt2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp tablepostgres=# create table tab2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp tableThanks,
Prabhat SahuGTT 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/>--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/ <https://www.enterprisedb.com/>
The Enterprise PostgreSQL Company
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 TABLEpostgres=# 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 TABLEpostgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
ERROR: constraints on temporary tables may reference only temporary tablespostgres=# create global temporary table gtt2 (c1 int references tmp1(c1) );
ERROR: constraints on temporary tables may reference only temporary tablesThanks,
Prabhat SahuOn 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 directoryis 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 TABLEpostgres=# create global temporary table gtt2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp tablepostgres=# create table tab2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp tableThanks,
Prabhat SahuGTT 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/>--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/ <https://www.enterprisedb.com/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+ int natts = 0;
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temp table yet");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ /* todo */
+ if (entry->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ Assert(entry->relid == reloid);
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == 0)
+ {
+ entry->attnum[i] = attnum;
+ break;
+ }
+ else if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < entry->natts);
+ Assert(entry->att_stat_tups[i] == NULL);
+ entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return NULL;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ return entry->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+ MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ HeapTuple tuple;
+ Relation rel = NULL;
+ int attnum = PG_GETARG_INT32(1);
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Tuplestorestate *tupstore;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ tuple = get_gtt_att_statistic(reloid, attnum, false);
+ if (tuple)
+ {
+ Datum values[31];
+ bool isnull[31];
+ HeapTuple res = NULL;
+
+ memset(&values, 0, sizeof(values));
+ memset(&isnull, 0, sizeof(isnull));
+ heap_deform_tuple(tuple, sd, values, isnull);
+ res = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, res);
+ }
+
+ table_close(rel, NoLock);
+ table_close(pg_tatistic, AccessShareLock);
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ if (get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid))
+ {
+ Datum values[5];
+ bool isnull[5];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(relpages);
+ values[1] = Float4GetDatum((float4)reltuples);
+ values[2] = Int32GetDatum(relallvisible);
+ values[3] = UInt32GetDatum(relfrozenxid);
+ values[4] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ map = copy_active_gtt_bitmap(rel->rd_node);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ int i = 0;
+ int j = 0;
+ uint32 oldest = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ if (max_active_gtt <= 0)
+ return (Datum) 0;
+
+ if (RecoveryInProgress())
+ return (Datum) 0;
+
+ pids = palloc0(sizeof(int) * num_xid);
+ xids = palloc0(sizeof(int) * num_xid);
+ oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+ if (i > 0)
+ {
+ if (i > 0)
+ {
+ pids[i] = 0;
+ xids[i] = oldest;
+ i++;
+ }
+
+ for(j = 0; j < i; j++)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(pids[j]);
+ values[1] = UInt32GetDatum(xids[j]);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ }
+ tuplestore_donestoring(tupstore);
+ pfree(pids);
+ pfree(xids);
+
+ return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(indexOid));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid relOid = index->rd_index->indrelid;
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ if (gtt_storage_attached(relOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
+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;
+
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 TABLEpostgres=# 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 TABLEpostgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
ERROR: constraints on temporary tables may reference only temporary tablespostgres=# 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 SahuOn 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 directoryis 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 TABLEpostgres=# create global temporary table gtt2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp tablepostgres=# create table tab2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp tableThanks,
Prabhat SahuGTT 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/>
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 TABLEpostgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
ERROR: constraints on temporary tables may reference only temporary
tablespostgres=# create global temporary table gtt2 (c1 int references
tmp1(c1) );
ERROR: constraints on temporary tables may reference only temporary
tablesFixed 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
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 TABLEpostgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
ERROR: constraints on temporary tables may reference only temporary tablespostgres=# create global temporary table gtt2 (c1 int references tmp1(c1) );
ERROR: constraints on temporary tables may reference only temporary tablesFixed 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=truepostgres=#
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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_gtt.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+ int natts = 0;
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temp table yet");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION)
+ gtt_storage_checkout(rnodes[i], true);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ /* todo */
+ if (entry->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ Assert(entry->relid == reloid);
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == 0)
+ {
+ entry->attnum[i] = attnum;
+ break;
+ }
+ else if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < entry->natts);
+ Assert(entry->att_stat_tups[i] == NULL);
+ entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return NULL;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ return entry->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+ MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ HeapTuple tuple;
+ Relation rel = NULL;
+ int attnum = PG_GETARG_INT32(1);
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Tuplestorestate *tupstore;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ tuple = get_gtt_att_statistic(reloid, attnum, false);
+ if (tuple)
+ {
+ Datum values[31];
+ bool isnull[31];
+ HeapTuple res = NULL;
+
+ memset(&values, 0, sizeof(values));
+ memset(&isnull, 0, sizeof(isnull));
+ heap_deform_tuple(tuple, sd, values, isnull);
+ res = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, res);
+ }
+
+ table_close(rel, NoLock);
+ table_close(pg_tatistic, AccessShareLock);
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ if (get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid))
+ {
+ Datum values[5];
+ bool isnull[5];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(relpages);
+ values[1] = Float4GetDatum((float4)reltuples);
+ values[2] = Int32GetDatum(relallvisible);
+ values[3] = UInt32GetDatum(relfrozenxid);
+ values[4] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ map = copy_active_gtt_bitmap(rel->rd_node);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ int i = 0;
+ int j = 0;
+ uint32 oldest = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ if (max_active_gtt <= 0)
+ return (Datum) 0;
+
+ if (RecoveryInProgress())
+ return (Datum) 0;
+
+ pids = palloc0(sizeof(int) * num_xid);
+ xids = palloc0(sizeof(int) * num_xid);
+ oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+ if (i > 0)
+ {
+ if (i > 0)
+ {
+ pids[i] = 0;
+ xids[i] = oldest;
+ i++;
+ }
+
+ for(j = 0; j < i; j++)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(pids[j]);
+ values[1] = UInt32GetDatum(xids[j]);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ }
+ tuplestore_donestoring(tupstore);
+ pfree(pids);
+ pfree(xids);
+
+ return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(indexOid));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid relOid = index->rd_index->indrelid;
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ if (gtt_storage_attached(relOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
+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;
+
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
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
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
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
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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ {
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ }
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+ int natts = 0;
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ elog(ERROR, "backend %d relid %u already exists in global temporary table local hash",
+ MyBackendId, relid);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry->spcnode = rnode.spcNode;
+ entry->relpages = 0;
+ entry->reltuples = 0;
+ entry->relallvisible = 0;
+ entry->relfrozenxid = InvalidTransactionId;
+ entry->relminmxid = InvalidMultiXactId;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+ }
+
+ entry->relfrozenxid = RecentXmin;
+ entry->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(rnode);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, NULL);
+
+ if (entry)
+ {
+ int i;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(entry->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(entry->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ RelFileNode rnode;
+ rnode.spcNode = entry->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = entry->relid;
+ gtt_storage_checkout(rnode, false, isCommit);
+ }
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->att_stat_tups[i])
+ {
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ }
+ }
+
+ if (entry->attnum)
+ pfree(entry->attnum);
+
+ if (entry->att_stat_tups)
+ pfree(entry->att_stat_tups);
+ }
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, &found);
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ int nrels = 0,
+ maxrels = 0;
+ SMgrRelation *srels = NULL;
+ RelFileNode *rnodes = NULL;
+ char *relkinds = NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+
+ rnode.spcNode = entry->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = entry->relid;
+
+ srel = smgropen(rnode, MyBackendId);
+
+ /* allocate the initial array, or extend it, if needed */
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxrels);
+ rnodes = palloc(sizeof(RelFileNode) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+ rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ srels[nrels] = srel;
+ rnodes[nrels] = rnode;
+ relkinds[nrels] = entry->relkind;
+ nrels++;
+ }
+
+ if (nrels > 0)
+ {
+ int i;
+
+ smgrdounlinkall(srels, nrels, false);
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(rnodes[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ /* todo */
+ if (entry->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ Assert(entry->relid == reloid);
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == 0)
+ {
+ entry->attnum[i] = attnum;
+ break;
+ }
+ else if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < entry->natts);
+ Assert(entry->att_stat_tups[i] == NULL);
+ entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return NULL;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ return entry->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+ MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ HeapTuple tuple;
+ Relation rel = NULL;
+ int attnum = PG_GETARG_INT32(1);
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Tuplestorestate *tupstore;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ tuple = get_gtt_att_statistic(reloid, attnum, false);
+ if (tuple)
+ {
+ Datum values[31];
+ bool isnull[31];
+ HeapTuple res = NULL;
+
+ memset(&values, 0, sizeof(values));
+ memset(&isnull, 0, sizeof(isnull));
+ heap_deform_tuple(tuple, sd, values, isnull);
+ res = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, res);
+ }
+
+ table_close(rel, NoLock);
+ table_close(pg_tatistic, AccessShareLock);
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ if (get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid))
+ {
+ Datum values[5];
+ bool isnull[5];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(relpages);
+ values[1] = Float4GetDatum((float4)reltuples);
+ values[2] = Int32GetDatum(relallvisible);
+ values[3] = UInt32GetDatum(relfrozenxid);
+ values[4] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ map = copy_active_gtt_bitmap(rel->rd_node);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ int i = 0;
+ int j = 0;
+ uint32 oldest = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ if (max_active_gtt <= 0)
+ return (Datum) 0;
+
+ if (RecoveryInProgress())
+ return (Datum) 0;
+
+ pids = palloc0(sizeof(int) * num_xid);
+ xids = palloc0(sizeof(int) * num_xid);
+ oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+ if (i > 0)
+ {
+ if (i > 0)
+ {
+ pids[i] = 0;
+ xids[i] = oldest;
+ i++;
+ }
+
+ for(j = 0; j < i; j++)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(pids[j]);
+ values[1] = UInt32GetDatum(xids[j]);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ }
+ tuplestore_donestoring(tupstore);
+ pfree(pids);
+ pfree(xids);
+
+ return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(indexOid));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid relOid = index->rd_index->indrelid;
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ if (gtt_storage_attached(relOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ int i;
+ Oid toastrelid;
+
+ if (operation != CMD_INSERT)
+ return;
+
+ if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+ return;
+
+ if (!RELATION_IS_GLOBAL_TEMP(relation))
+ return;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_attached(relation->rd_node.relNode))
+ return;
+
+ RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+ for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+ {
+ Relation index = resultRelInfo->ri_IndexRelationDescs[i];
+ IndexInfo *info = resultRelInfo->ri_IndexRelationInfo[i];
+
+ Assert(index->rd_index->indisvalid);
+ Assert(index->rd_index->indislive);
+ Assert(index->rd_index->indisready);
+
+ index_build(relation, index, info, true, false);
+ }
+
+ toastrelid = relation->rd_rel->reltoastrelid;
+ if (OidIsValid(toastrelid))
+ {
+ Relation toastrel;
+ ListCell *indlist;
+
+ toastrel = table_open(toastrelid, RowExclusiveLock);
+ RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+ foreach(indlist, RelationGetIndexList(toastrel))
+ {
+ Oid indexId = lfirst_oid(indlist);
+ Relation currentIndex;
+ IndexInfo *indexInfo;
+
+ currentIndex = index_open(indexId, RowExclusiveLock);
+
+ indexInfo = BuildDummyIndexInfo(currentIndex);
+ index_build(toastrel, currentIndex, indexInfo, true, false);
+ index_close(currentIndex, NoLock);
+ }
+
+ table_close(toastrel, NoLock);
+ }
+
+ return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46..491db16 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
WHERE NOT pg_is_other_temp_schema(N.oid)
AND relkind = 'S';
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stavalues1
+ WHEN s.stakind2 = 1 THEN s.stavalues2
+ WHEN s.stakind3 = 1 THEN s.stavalues3
+ WHEN s.stakind4 = 1 THEN s.stavalues4
+ WHEN s.stakind5 = 1 THEN s.stavalues5
+ END AS most_common_vals,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stanumbers1
+ WHEN s.stakind2 = 1 THEN s.stanumbers2
+ WHEN s.stakind3 = 1 THEN s.stanumbers3
+ WHEN s.stakind4 = 1 THEN s.stanumbers4
+ WHEN s.stakind5 = 1 THEN s.stanumbers5
+ END AS most_common_freqs,
+ CASE
+ WHEN s.stakind1 = 2 THEN s.stavalues1
+ WHEN s.stakind2 = 2 THEN s.stavalues2
+ WHEN s.stakind3 = 2 THEN s.stavalues3
+ WHEN s.stakind4 = 2 THEN s.stavalues4
+ WHEN s.stakind5 = 2 THEN s.stavalues5
+ END AS histogram_bounds,
+ CASE
+ WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+ WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+ WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+ WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+ WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+ END AS correlation,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stavalues1
+ WHEN s.stakind2 = 4 THEN s.stavalues2
+ WHEN s.stakind3 = 4 THEN s.stavalues3
+ WHEN s.stakind4 = 4 THEN s.stavalues4
+ WHEN s.stakind5 = 4 THEN s.stavalues5
+ END AS most_common_elems,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stanumbers1
+ WHEN s.stakind2 = 4 THEN s.stanumbers2
+ WHEN s.stakind3 = 4 THEN s.stanumbers3
+ WHEN s.stakind4 = 4 THEN s.stanumbers4
+ WHEN s.stakind5 = 4 THEN s.stanumbers5
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN s.stakind1 = 5 THEN s.stanumbers1
+ WHEN s.stakind2 = 5 THEN s.stanumbers2
+ WHEN s.stakind3 = 5 THEN s.stanumbers3
+ WHEN s.stakind4 = 5 THEN s.stanumbers4
+ WHEN s.stakind5 = 5 THEN s.stanumbers5
+ END AS elem_count_histogram
+ FROM
+ pg_class c
+ JOIN pg_attribute a ON c.oid = a.attrelid
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
CREATE VIEW pg_stats WITH (security_barrier) AS
SELECT
nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..7efc5a9 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
#include "commands/dbcommands.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int acquire_inherited_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
static void update_attstats(Oid relid, bool inh,
- int natts, VacAttrStats **vacattrstats);
+ int natts, VacAttrStats **vacattrstats, char relpersistence);
static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
return;
}
+ if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+ !gtt_storage_attached(onerel->rd_node.relNode))
+ {
+ relation_close(onerel, ShareUpdateExclusiveLock);
+ return;
+ }
+
/*
* We can ANALYZE any table except pg_statistic. See update_attstats
*/
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* pg_statistic for columns we didn't process, we leave them alone.)
*/
update_attstats(RelationGetRelid(onerel), inh,
- attr_cnt, vacattrstats);
+ attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
for (ind = 0; ind < nindexes; ind++)
{
AnlIndexData *thisdata = &indexdata[ind];
update_attstats(RelationGetRelid(Irel[ind]), false,
- thisdata->attr_cnt, thisdata->vacattrstats);
+ thisdata->attr_cnt, thisdata->vacattrstats,
+ RelationGetRelPersistence(Irel[ind]));
}
/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
* by taking a self-exclusive lock on the relation in analyze_rel().
*/
static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
{
Relation sd;
int attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
}
}
- /* Is there already a pg_statistic tuple for this attribute? */
- oldtup = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(relid),
- Int16GetDatum(stats->attr->attnum),
- BoolGetDatum(inh));
-
- if (HeapTupleIsValid(oldtup))
+ /* Update column statistic to localhash, not catalog */
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
{
- /* Yes, replace it */
- stup = heap_modify_tuple(oldtup,
- RelationGetDescr(sd),
- values,
- nulls,
- replaces);
- ReleaseSysCache(oldtup);
- CatalogTupleUpdate(sd, &stup->t_self, stup);
+ up_gtt_att_statistic(relid,
+ stats->attr->attnum,
+ inh,
+ natts,
+ RelationGetDescr(sd),
+ values,
+ nulls);
}
else
{
- /* No, insert new tuple */
- stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
- CatalogTupleInsert(sd, stup);
- }
+ /* Is there already a pg_statistic tuple for this attribute? */
+ oldtup = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(relid),
+ Int16GetDatum(stats->attr->attnum),
+ BoolGetDatum(inh));
- heap_freetuple(stup);
+ if (HeapTupleIsValid(oldtup))
+ {
+ /* Yes, replace it */
+ stup = heap_modify_tuple(oldtup,
+ RelationGetDescr(sd),
+ values,
+ nulls,
+ replaces);
+ ReleaseSysCache(oldtup);
+ CatalogTupleUpdate(sd, &stup->t_self, stup);
+ }
+ else
+ {
+ /* No, insert new tuple */
+ stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+ CatalogTupleInsert(sd, stup);
+ }
+
+ heap_freetuple(stup);
+ }
}
table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
errmsg("cannot vacuum temporary tables of other sessions")));
}
+ /* not support cluster global temp table yet */
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("not support cluster global temporary tables yet")));
+
/*
* Also check for active uses of the relation in the current transaction,
* including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e79ede4..078694d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
#include "catalog/dependency.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
#include "commands/copy.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
@@ -1062,7 +1063,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
Assert(rel);
/* check read-only transaction and parallel mode */
- if (XactReadOnly && !rel->rd_islocaltemp)
+ if (XactReadOnly && !RELATION_IS_TEMP(rel))
PreventCommandIfReadOnly("COPY FROM");
cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2840,6 +2841,8 @@ CopyFrom(CopyState cstate)
ExecOpenIndices(resultRelInfo, false);
+ init_gtt_storage(CMD_INSERT, resultRelInfo);
+
estate->es_result_relations = resultRelInfo;
estate->es_num_result_relations = 1;
estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 3f3a89f..928c01e 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2586,6 +2586,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
!isTempNamespace(classtuple->relnamespace))
continue;
+ /* not support reindex on global temp table, so skip it */
+ if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ ereport(WARNING,
+ (errmsg("global temp table \"%s.%s\" skip reindexed",
+ get_namespace_name(get_rel_namespace(relid)),
+ get_rel_name(relid))));
+ continue;
+ }
+
/* Check user/system classification, and optionally skip */
if (objectKind == REINDEX_OBJECT_SYSTEM &&
!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..deecde8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* transaction.
*/
relpersistence = get_rel_persistence(relid);
- if (relpersistence == RELPERSISTENCE_TEMP)
+ if (relpersistence == RELPERSISTENCE_TEMP ||
+ relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..fada1ac 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
#include "catalog/objectaccess.h"
#include "catalog/pg_sequence.h"
#include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
#include "commands/defrem.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
*/
static SeqTableData *last_used_seq = NULL;
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
static Relation lock_and_open_sequence(SeqTable seq);
static void create_seq_hashtable(void);
static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
List **owned_by);
static void do_setval(Oid relid, int64 next, bool iscalled);
static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
/*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
/* now initialize the sequence's data */
tuple = heap_form_tuple(tupDesc, value, null);
- fill_seq_with_data(rel, tuple);
+ fill_seq_with_data(rel, tuple, InvalidBuffer);
/* process OWNED BY if given */
if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
/*
* Insert the modified tuple into the new storage file.
*/
- fill_seq_with_data(seq_rel, tuple);
+ fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
/* Clear local cache so that we don't think we have cached numbers */
/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
* Initialize a sequence's relation with the specified tuple as content
*/
static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
{
- Buffer buf;
Page page;
sequence_magic *sm;
OffsetNumber offnum;
+ bool lockBuffer = false;
/* Initialize first page of relation with special magic number */
- buf = ReadBuffer(rel, P_NEW);
- Assert(BufferGetBlockNumber(buf) == 0);
+ if (buf == InvalidBuffer)
+ {
+ buf = ReadBuffer(rel, P_NEW);
+ Assert(BufferGetBlockNumber(buf) == 0);
+ lockBuffer = true;
+ }
page = BufferGetPage(buf);
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
/* Now insert sequence tuple */
- LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+ if (lockBuffer)
+ LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
/*
* Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
END_CRIT_SECTION();
- UnlockReleaseBuffer(buf);
+ if (lockBuffer)
+ UnlockReleaseBuffer(buf);
}
/*
@@ -502,7 +511,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
/*
* Insert the modified tuple into the new storage file.
*/
- fill_seq_with_data(seqrel, newdatatuple);
+ fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
}
/* process OWNED BY if given */
@@ -611,7 +620,7 @@ nextval_internal(Oid relid, bool check_permissions)
RelationGetRelationName(seqrel))));
/* read-only transactions may only modify temp sequences */
- if (!seqrel->rd_islocaltemp)
+ if (!RELATION_IS_TEMP(seqrel))
PreventCommandIfReadOnly("nextval()");
/*
@@ -936,7 +945,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
ReleaseSysCache(pgstuple);
/* read-only transactions may only modify temp sequences */
- if (!seqrel->rd_islocaltemp)
+ if (!RELATION_IS_TEMP(seqrel))
PreventCommandIfReadOnly("setval()");
/*
@@ -1154,6 +1163,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
/* Return results */
*p_elm = elm;
*p_rel = seqrel;
+
+ if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+ !gtt_storage_attached(seqrel->rd_node.relNode))
+ {
+ RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+ gtt_init_seq(seqrel);
+ }
}
@@ -1954,3 +1970,46 @@ seq_mask(char *page, BlockNumber blkno)
mask_unused_space(page);
}
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+ HeapTuple seqtuple;
+ Form_pg_sequence seqform;
+ int64 start;
+
+ seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+ if (!HeapTupleIsValid(seqtuple))
+ elog(ERROR, "cache lookup failed for sequence %u",
+ seqid);
+
+ seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+ start = seqform->seqstart;
+ ReleaseSysCache(seqtuple);
+
+ return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+ /* Initialize sequence for global temporary tables */
+ Datum value[SEQ_COL_LASTCOL] = {0};
+ bool null[SEQ_COL_LASTCOL] = {false};
+ HeapTuple tuple;
+ int64 startv = get_seqence_start_value(RelationGetRelid(rel));
+
+ /*
+ * last_value from pg_sequence.seqstart
+ * log_cnt = 0
+ * is_called = false
+ */
+ value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+ tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+ fill_seq_with_data(rel, tuple, InvalidBuffer);
+ heap_freetuple(tuple);
+
+ return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7a13b97..536083e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
#include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
#include "commands/cluster.h"
#include "commands/comment.h"
#include "commands/defrem.h"
@@ -553,6 +554,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
Relation partitionTbl);
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
/* ----------------------------------------------------------------
@@ -598,6 +600,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
LOCKMODE parentLockmode;
const char *accessMethod = NULL;
Oid accessMethodId = InvalidOid;
+ OnCommitAction oncommit_action = ONCOMMIT_NOOP;
/*
* Truncate relname to appropriate length (probably a waste of time, as
@@ -608,8 +611,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Check consistency of arguments
*/
+ /* global temp table same as local temp table */
if (stmt->oncommit != ONCOMMIT_NOOP
- && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+ && !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+ stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
@@ -639,7 +644,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* code. This is needed because calling code might not expect untrusted
* tables to appear in pg_temp at the front of its search path.
*/
- if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+ /* global temp table same as local temp table */
+ if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+ stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
&& InSecurityRestrictedOperation())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -740,6 +747,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Parse and validate reloptions, if any.
*/
+ /* global temp table */
+ oncommit_action = gtt_oncommit_option(stmt->options);
+ if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+ {
+ /* check parent table*/
+ if (inheritOids)
+ {
+ Oid parent = linitial_oid(inheritOids);
+ Relation relation = table_open(parent, NoLock);
+
+ if (!RELATION_IS_GLOBAL_TEMP(relation))
+ elog(ERROR, "The parent table must be global temporary table");
+
+ table_close(relation, NoLock);
+ }
+
+ if (oncommit_action != ONCOMMIT_NOOP)
+ {
+ if (stmt->oncommit != ONCOMMIT_NOOP)
+ elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+
+ stmt->oncommit = oncommit_action;
+ }
+ else
+ {
+ DefElem *opt = makeNode(DefElem);
+
+ opt->type = T_DefElem;
+ opt->defnamespace = NULL;
+ opt->defname = "on_commit_delete_rows";
+ opt->defaction = DEFELEM_UNSPEC;
+
+ /* use reloptions to remember on commit clause */
+ if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+ opt->arg = (Node *)makeString("true");
+ else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+ opt->arg = (Node *)makeString("false");
+ else if (stmt->oncommit == ONCOMMIT_NOOP)
+ opt->arg = (Node *)makeString("false");
+ else
+ elog(ERROR, "global temp table not support on commit drop clause");
+
+ stmt->options = lappend(stmt->options, opt);
+ }
+ }
+ else if (oncommit_action != ONCOMMIT_NOOP)
+ elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
true, false);
@@ -1824,7 +1880,8 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
* table or the current physical file to be thrown away anyway.
*/
if (rel->rd_createSubid == mySubid ||
- rel->rd_newRelfilenodeSubid == mySubid)
+ rel->rd_newRelfilenodeSubid == mySubid ||
+ RELATION_IS_GLOBAL_TEMP(rel))
{
/* Immediate, non-rollbackable truncation is OK */
heap_truncate_one_rel(rel);
@@ -3567,6 +3624,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
/* Caller is required to provide an adequate lock. */
rel = relation_open(context->relid, NoLock);
+ /* We allow to alter global temp table only this session use it */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (is_other_backend_use_gtt(rel->rd_node))
+ elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+ RelationGetRelationName(rel));
+ }
+
CheckTableNotInUse(rel, "ALTER TABLE");
ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8179,6 +8244,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("constraints on temporary tables must involve temporary tables of this session")));
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("constraints on global temporary tables may reference only global temporary tables")));
+ break;
}
/*
@@ -12647,6 +12718,12 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
if (defList == NIL && operation != AT_ReplaceRelOptions)
return; /* nothing to do */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+ elog(ERROR, "The global temp table does not allow modification on commit action.");
+ }
+
pgclass = table_open(RelationRelationId, RowExclusiveLock);
/* Fetch heap tuple */
@@ -12849,6 +12926,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
*/
rel = relation_open(tableOid, lockmode);
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ elog(ERROR, "not support alter table set tablespace on global temp table");
+
/*
* No work if no change in tablespace.
*/
@@ -13221,7 +13301,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
* NOTE: any conflict in relfilenode value will be caught in
* RelationCreateStorage().
*/
- RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+ RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
/* copy main fork */
RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14628,7 +14708,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
*/
switch (rel->rd_rel->relpersistence)
{
+ /* global temp table same as local temp table */
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_GLOBAL_TEMP:
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17220,3 +17302,28 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
table_close(rel, NoLock);
}
}
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+ ListCell *listptr;
+ OnCommitAction action = ONCOMMIT_NOOP;
+
+ foreach(listptr, options)
+ {
+ DefElem *def = (DefElem *) lfirst(listptr);
+
+ if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+ {
+ if (defGetBoolean(def))
+ action = ONCOMMIT_DELETE_ROWS;
+ else
+ action = ONCOMMIT_PRESERVE_ROWS;
+
+ break;
+ }
+ }
+
+ return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..f58c44d 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
HeapTuple ctup;
Form_pg_class pgcform;
bool dirty;
+ bool is_gtt = false;
+
+ /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+ if (RELATION_IS_GLOBAL_TEMP(relation))
+ {
+ is_gtt = true;
+ up_gtt_relstats(relation,
+ num_pages, num_tuples,
+ num_all_visible_pages,
+ frozenxid, minmulti);
+ }
rd = table_open(RelationRelationId, RowExclusiveLock);
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
/* Apply statistical updates, if any, to copied tuple */
dirty = false;
- if (pgcform->relpages != (int32) num_pages)
+
+ if (is_gtt)
+ relation->rd_rel->relpages = (int32) num_pages;
+ else if (pgcform->relpages != (int32) num_pages)
{
pgcform->relpages = (int32) num_pages;
dirty = true;
}
- if (pgcform->reltuples != (float4) num_tuples)
+
+ if (is_gtt)
+ relation->rd_rel->reltuples = (float4) num_tuples;
+ else if (pgcform->reltuples != (float4) num_tuples)
{
pgcform->reltuples = (float4) num_tuples;
dirty = true;
}
- if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+ if (is_gtt)
+ relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+ else if (pgcform->relallvisible != (int32) num_all_visible_pages)
{
pgcform->relallvisible = (int32) num_all_visible_pages;
dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
* This should match vac_update_datfrozenxid() concerning what we consider
* to be "in the future".
*/
- if (TransactionIdIsNormal(frozenxid) &&
+ if (!is_gtt &&
+ TransactionIdIsNormal(frozenxid) &&
pgcform->relfrozenxid != frozenxid &&
(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
}
/* Similarly for relminmxid */
- if (MultiXactIdIsValid(minmulti) &&
+ if (!is_gtt &&
+ MultiXactIdIsValid(minmulti) &&
pgcform->relminmxid != minmulti &&
(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
continue;
}
+ /* global temp table relstats not in pg_class */
+ if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+
/*
* Some table AMs might not need per-relation xid / multixid horizons.
* It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
Assert(TransactionIdIsNormal(newFrozenXid));
Assert(MultiXactIdIsValid(newMinMulti));
+ /*
+ * Global temp table get frozenxid from MyProc
+ * to avoid the vacuum truncate clog that gtt need.
+ */
+ if (max_active_gtt > 0)
+ {
+ TransactionId oldest_gtt_frozenxid =
+ list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+ if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+ TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+ {
+ ereport(WARNING,
+ (errmsg("global temp table oldest FrozenXid is far in the past"),
+ errhint("please truncate them or kill those sessions that use them.")));
+ newFrozenXid = oldest_gtt_frozenxid;
+ }
+ }
+
/* Now fetch the pg_database tuple we need to update. */
relation = table_open(DatabaseRelationId, RowExclusiveLock);
@@ -1778,6 +1824,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return false;
}
+ if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+ !gtt_storage_attached(onerel->rd_node.relNode))
+ {
+ ereport(WARNING,
+ (errmsg("skipping vacuum empty global temp table \"%s\"",
+ RelationGetRelationName(onerel))));
+ relation_close(onerel, lmode);
+ PopActiveSnapshot();
+ CommitTransactionCommand();
+ return false;
+ }
+
/*
* Silently ignore tables that are temp tables of other backends ---
* trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 28130fb..dcb4a0b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
if (isTempNamespace(get_rel_namespace(rte->relid)))
continue;
+ if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+
PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
}
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index c13b1d3..253054d 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
#include "foreign/fdwapi.h"
@@ -543,6 +544,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
(node != NULL &&
node->onConflictAction != ONCONFLICT_NONE));
+ init_gtt_storage(mtstate->operation, leaf_part_rri);
+
/*
* Build WITH CHECK OPTION constraints for the partition. Note that we
* didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
#include "access/tableam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
#include "commands/trigger.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecOpenIndices(resultRelInfo,
node->onConflictAction != ONCONFLICT_NONE);
+ init_gtt_storage(operation, resultRelInfo);
+
/*
* If this is an UPDATE and a BEFORE UPDATE trigger is present, the
* trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte)
{
+ char relpersistence;
+
/*
* The flag has previously been initialized to false, so we can just
* return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* the rest of the necessary infrastructure right now anyway. So
* for now, bail out if we see a temporary table.
*/
- if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+ /* global temp table is same as local temp table */
+ relpersistence = get_rel_persistence(rte->relid);
+
+ if (relpersistence == RELPERSISTENCE_TEMP ||
+ relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
return;
/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b44efd6..14dbaaf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
* Furthermore, any index predicate or index expressions must be parallel
* safe.
*/
+ /* global temp table is same as local temp table */
if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+ heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..93c6d97 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
#include "catalog/pg_am.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
continue;
}
+ /* Ignore empty index for global temp table */
+ if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+ !gtt_storage_attached(indexRelation->rd_node.relNode))
+ {
+ index_close(indexRelation, NoLock);
+ continue;
+ }
+
/*
* If the index is valid, but cannot yet be used, ignore it; but
* mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412..0685c1c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("materialized views must not use temporary tables or views")));
+ if (is_query_using_gtt(query))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialized views must not use global temporary tables or views")));
+
/*
* A materialized view would either need to save parameters for use in
* maintaining/loading the data or prohibit them entirely. The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 96e7fdb..30f585c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp: TEMPORARY { $$ = RELPERSISTENCE_TEMP; }
| LOCAL TEMP { $$ = RELPERSISTENCE_TEMP; }
| GLOBAL TEMPORARY
{
- ereport(WARNING,
- (errmsg("GLOBAL is deprecated in temporary table creation"),
- parser_errposition(@1)));
- $$ = RELPERSISTENCE_TEMP;
+ $$ = RELPERSISTENCE_GLOBAL_TEMP;
}
| GLOBAL TEMP
{
- ereport(WARNING,
- (errmsg("GLOBAL is deprecated in temporary table creation"),
- parser_errposition(@1)));
- $$ = RELPERSISTENCE_TEMP;
+ $$ = RELPERSISTENCE_GLOBAL_TEMP;
}
| UNLOGGED { $$ = RELPERSISTENCE_UNLOGGED; }
| /*EMPTY*/ { $$ = RELPERSISTENCE_PERMANENT; }
@@ -11594,19 +11588,13 @@ OptTempTableName:
}
| GLOBAL TEMPORARY opt_table qualified_name
{
- ereport(WARNING,
- (errmsg("GLOBAL is deprecated in temporary table creation"),
- parser_errposition(@1)));
$$ = $4;
- $$->relpersistence = RELPERSISTENCE_TEMP;
+ $$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
}
| GLOBAL TEMP opt_table qualified_name
{
- ereport(WARNING,
- (errmsg("GLOBAL is deprecated in temporary table creation"),
- parser_errposition(@1)));
$$ = $4;
- $$->relpersistence = RELPERSISTENCE_TEMP;
+ $$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
}
| UNLOGGED opt_table qualified_name
{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
List **colnames, List **colvars);
static int specialAttNum(const char *attname);
static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
/*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
isQueryUsingTempRelation_walker,
context);
}
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Query))
+ {
+ Query *query = (Query *) node;
+ ListCell *rtable;
+
+ foreach(rtable, query->rtable)
+ {
+ RangeTblEntry *rte = lfirst(rtable);
+
+ if (rte->rtekind == RTE_RELATION)
+ {
+ Relation rel = relation_open(rte->relid, AccessShareLock);
+ char relpersistence = rel->rd_rel->relpersistence;
+
+ relation_close(rel, AccessShareLock);
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ return true;
+ }
+ }
+
+ return query_tree_walker(query,
+ is_query_using_gtt_walker,
+ context,
+ QTW_IGNORE_JOINALIASES);
+ }
+
+ return expression_tree_walker(node,
+ is_query_using_gtt_walker,
+ context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+ return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index af77f18..67d34f0 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
seqstmt->options = seqoptions;
+ if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
/*
* If a sequence data type was specified, add it to the options. Prepend
* to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index e3a43d3..ca460ca 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2087,6 +2087,11 @@ do_autovacuum(void)
}
continue;
}
+ else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* autovacuum skip vacuum global temp table */
+ continue;
+ }
/* Fetch reloptions and the pgstat entry for this table */
relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2153,7 +2158,9 @@ do_autovacuum(void)
/*
* We cannot safely process other backends' temp tables, so skip 'em.
*/
- if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+ /* autovacuum skip vacuum global temp table */
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP ||
+ classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
continue;
relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 5880054..98f7b43 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
#include "executor/instrument.h"
#include "lib/binaryheap.h"
#include "miscadmin.h"
@@ -52,6 +53,7 @@
#include "utils/rel.h"
#include "utils/resowner_private.h"
#include "utils/timestamp.h"
+#include "utils/guc.h"
/* Note: these two macros only work on shared buffers, not local ones! */
@@ -2809,6 +2811,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
BlockNumber
RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
{
+ /*
+ * When this backend not init gtt storage
+ * return 0
+ */
+ if (RELATION_IS_GLOBAL_TEMP(relation) &&
+ !gtt_storage_attached(relation->rd_node.relNode))
+ {
+ return 0;
+ }
+
switch (relation->rd_rel->relkind)
{
case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
#include "access/nbtree.h"
#include "access/subtrans.h"
#include "access/twophase.h"
+#include "catalog/storage_gtt.h"
#include "commands/async.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
size = add_size(size, BTreeShmemSize());
size = add_size(size, SyncScanShmemSize());
size = add_size(size, AsyncShmemSize());
+ size = add_size(size, active_gtt_shared_hash_size());
#ifdef EXEC_BACKEND
size = add_size(size, ShmemBackendArraySize());
#endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
SUBTRANSShmemInit();
MultiXactShmemInit();
InitBufferPool();
+ /* global temporary table */
+ active_gtt_shared_hash_init();
/*
* Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 4a5b26c..9754168 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
#include "utils/builtins.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
+#include "utils/guc.h"
#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var))))
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
LWLockRelease(ProcArrayLock);
}
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+ ProcArrayStruct *arrayP = procArray;
+ TransactionId result = InvalidTransactionId;
+ int index;
+ int flags = 0;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return 0;
+
+ if (max_size > 0)
+ {
+ Assert(pids);
+ Assert(xids);
+ Assert(n);
+ *n = 0;
+ }
+
+ if (max_active_gtt <= 0)
+ return InvalidTransactionId;
+
+ if (RecoveryInProgress())
+ return InvalidTransactionId;
+
+ flags |= PROC_IS_AUTOVACUUM;
+ flags |= PROC_IN_LOGICAL_DECODING;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+ if (max_size > 0 && max_size < arrayP->numProcs)
+ {
+ LWLockRelease(ProcArrayLock);
+ elog(ERROR, "list_all_gtt_frozenxids require more array");
+ }
+
+ for (index = 0; index < arrayP->numProcs; index++)
+ {
+ int pgprocno = arrayP->pgprocnos[index];
+ volatile PGPROC *proc = &allProcs[pgprocno];
+ volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+ if (pgxact->vacuumFlags & flags)
+ continue;
+
+ if (proc->databaseId == MyDatabaseId &&
+ TransactionIdIsNormal(proc->session_gtt_frozenxid))
+ {
+ if (result == InvalidTransactionId)
+ result = proc->session_gtt_frozenxid;
+ else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+ result = proc->session_gtt_frozenxid;
+
+ if (max_size > 0)
+ {
+ pids[i] = proc->pid;
+ xids[i] = proc->session_gtt_frozenxid;
+ i++;
+ }
+ }
+ }
+ LWLockRelease(ProcArrayLock);
+
+ if (max_size > 0)
+ *n = i;
+
+ return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index eb321f7..3893cef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->tempNamespaceId = InvalidOid;
+ MyProc->session_gtt_frozenxid = InvalidTransactionId; /* init session level gtt frozenxid */
MyProc->isBackgroundWorker = IsBackgroundWorker;
MyPgXact->delayChkpt = false;
MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->tempNamespaceId = InvalidOid;
+ MyProc->session_gtt_frozenxid = InvalidTransactionId; /* init session level gtt frozenxid */
MyProc->isBackgroundWorker = IsBackgroundWorker;
MyPgXact->delayChkpt = false;
MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
Assert(backend != InvalidBackendId);
}
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ backend = BackendIdForTempRelations();
+ break;
default:
elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0be26fe..c169c99 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
#include "executor/nodeAgg.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -4570,12 +4571,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
}
else if (index->indpred == NIL)
{
- vardata->statsTuple =
- SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(index->indexoid),
- Int16GetDatum(pos + 1),
- BoolGetDatum(false));
- vardata->freefunc = ReleaseSysCache;
+ char rel_persistence = get_rel_persistence(index->indexoid);
+
+ if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata->statsTuple =
+ get_gtt_att_statistic(index->indexoid,
+ Int16GetDatum(pos + 1),
+ false);
+ vardata->freefunc = release_gtt_statistic_cache;
+ }
+ else
+ {
+ vardata->statsTuple =
+ SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(index->indexoid),
+ Int16GetDatum(pos + 1),
+ BoolGetDatum(false));
+ vardata->freefunc = ReleaseSysCache;
+ }
if (HeapTupleIsValid(vardata->statsTuple))
{
@@ -4700,15 +4714,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
}
else if (rte->rtekind == RTE_RELATION)
{
- /*
- * Plain table or parent of an inheritance appendrel, so look up the
- * column in pg_statistic
- */
- vardata->statsTuple = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(rte->relid),
- Int16GetDatum(var->varattno),
- BoolGetDatum(rte->inh));
- vardata->freefunc = ReleaseSysCache;
+ char rel_persistence = get_rel_persistence(rte->relid);
+
+ if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+ var->varattno,
+ rte->inh);
+ vardata->freefunc = release_gtt_statistic_cache;
+ }
+ else
+ {
+ /*
+ * Plain table or parent of an inheritance appendrel, so look up the
+ * column in pg_statistic
+ */
+ vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(rte->relid),
+ Int16GetDatum(var->varattno),
+ BoolGetDatum(rte->inh));
+ vardata->freefunc = ReleaseSysCache;
+ }
if (HeapTupleIsValid(vardata->statsTuple))
{
@@ -6090,6 +6116,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
{
/* Simple variable --- look to stats for the underlying table */
RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+ char rel_persistence = get_rel_persistence(rte->relid);
Assert(rte->rtekind == RTE_RELATION);
relid = rte->relid;
@@ -6107,6 +6134,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata.statsTuple = get_gtt_att_statistic(relid,
+ colnum,
+ rte->inh);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6118,6 +6152,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
}
else
{
+ char rel_persistence = get_rel_persistence(index->indexoid);
+
/* Expression --- maybe there are stats for the index itself */
relid = index->indexoid;
colnum = 1;
@@ -6133,6 +6169,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata.statsTuple = get_gtt_att_statistic(relid,
+ colnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7046,6 +7089,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
/* attempt to lookup stats in relation for this index column */
if (attnum != 0)
{
+ char rel_persistence = get_rel_persistence(rte->relid);
+
/* Simple variable -- look to stats for the underlying table */
if (get_relation_stats_hook &&
(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7058,6 +7103,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
elog(ERROR,
"no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata.statsTuple =
+ get_gtt_att_statistic(rte->relid,
+ attnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple =
@@ -7070,6 +7123,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
}
else
{
+ char rel_persistence = get_rel_persistence(index->indexoid);
+
/*
* Looks like we've found an expression column in the index. Let's
* see if there's any stats for it.
@@ -7089,6 +7144,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata.statsTuple =
+ get_gtt_att_statistic(index->indexoid,
+ attnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 3da90cb..97240ff 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
#include "catalog/pg_statistic.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
if (stawidth > 0)
return stawidth;
}
+ if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ tp = get_gtt_att_statistic(relid, attnum, false);
+ if (!HeapTupleIsValid(tp))
+ return 0;
+
+ stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+ if (stawidth > 0)
+ return stawidth;
+ else
+ return 0;
+ }
tp = SearchSysCache3(STATRELATTINH,
ObjectIdGetDatum(relid),
Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326..1cf7063 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
#include "catalog/pg_type.h"
#include "catalog/schemapg.h"
#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
#include "commands/policy.h"
#include "commands/trigger.h"
#include "miscadmin.h"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_islocaltemp = false;
}
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ {
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+
+ relation->rd_backend = BackendIdForTempRelations();
+ relation->rd_islocaltemp = false;
+ get_gtt_relstats(RelationGetRelid(relation),
+ &relpages,
+ &reltuples,
+ &relallvisible,
+ NULL, NULL);
+ relation->rd_rel->relpages = (int32)relpages;
+ relation->rd_rel->reltuples = (float4)reltuples;
+ relation->rd_rel->relallvisible = (int32)relallvisible;
+ }
+ break;
default:
elog(ERROR, "invalid relpersistence: %c",
relation->rd_rel->relpersistence);
@@ -1181,6 +1200,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
case RELKIND_PARTITIONED_INDEX:
Assert(relation->rd_rel->relam != InvalidOid);
RelationInitIndexAccessInfo(relation);
+ gtt_fix_index_state(relation);
break;
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
@@ -2220,6 +2240,8 @@ RelationReloadIndexInfo(Relation relation)
HeapTupleHeaderGetXmin(tuple->t_data));
ReleaseSysCache(tuple);
+
+ gtt_fix_index_state(relation);
}
/* Okay, now it's valid again */
@@ -3316,6 +3338,10 @@ RelationBuildLocalRelation(const char *relname,
rel->rd_backend = BackendIdForTempRelations();
rel->rd_islocaltemp = true;
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ rel->rd_backend = BackendIdForTempRelations();
+ rel->rd_islocaltemp = false;
+ break;
default:
elog(ERROR, "invalid relpersistence: %c", relpersistence);
break;
@@ -3430,6 +3456,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
TransactionId freezeXid = InvalidTransactionId;
RelFileNode newrnode;
+ if (RELATION_IS_GLOBAL_TEMP(relation))
+ elog(ERROR, "global temp table does not allow setting new relfilenode");
+
/* Allocate a new relfilenode */
newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
persistence);
@@ -3470,7 +3499,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
/* handle these directly, at least for now */
SMgrRelation srel;
- srel = RelationCreateStorage(newrnode, persistence);
+ srel = RelationCreateStorage(newrnode, persistence, relation);
smgrclose(srel);
}
break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index dbecc00..6acf3f3 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -140,6 +140,18 @@ char *GUC_check_errmsg_string;
char *GUC_check_errdetail_string;
char *GUC_check_errhint_string;
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define MIN_NUM_ACTIVE_GTT 0
+#define DEFAULT_NUM_ACTIVE_GTT 1000
+#define MAX_NUM_ACTIVE_GTT 1000000
+
+int max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2047,6 +2059,15 @@ static struct config_bool ConfigureNamesBool[] =
static struct config_int ConfigureNamesInt[] =
{
{
+ {"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+ gettext_noop("max active global temporary table."),
+ NULL
+ },
+ &max_active_gtt,
+ DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+ NULL, NULL, NULL
+ },
+ {
{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
gettext_noop("Forces a switch to the next WAL file if a "
"new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a12c8d0..58b22ea 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15595,6 +15595,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
{
char *ftoptions = NULL;
char *srvname = NULL;
+ char *table_type = NULL;
switch (tbinfo->relkind)
{
@@ -15646,9 +15647,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
binary_upgrade_set_pg_class_oids(fout, q,
tbinfo->dobj.catId.oid, false);
+ if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+ table_type = "UNLOGGED ";
+ else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ table_type = "GLOBAL TEMPORARY ";
+ else
+ table_type = "";
+
appendPQExpBuffer(q, "CREATE %s%s %s",
- tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
- "UNLOGGED " : "",
+ table_type,
reltypename,
qualrelname);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 5f9a102..3e40297 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
start_postmaster(&old_cluster, true);
/* Extract a list of databases and tables from the old cluster */
- get_db_and_rel_infos(&old_cluster);
+ get_db_and_rel_infos(&old_cluster, true);
init_tablespaces();
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
void
check_new_cluster(void)
{
- get_db_and_rel_infos(&new_cluster);
+ get_db_and_rel_infos(&new_cluster, false);
check_new_cluster_is_empty();
check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
bool is_new_db);
static void free_db_and_rel_infos(DbInfoArr *db_arr);
static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
static void free_rel_infos(RelInfoArr *rel_arr);
static void print_db_infos(DbInfoArr *dbinfo);
static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
*
* higher level routine to generate dbinfos for the database running
* on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
*/
void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
{
int dbnum;
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
get_db_infos(cluster);
for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
- get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+ get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
if (cluster == &old_cluster)
pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
* This allows later processing to match up old and new databases efficiently.
*/
static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
{
PGconn *conn = connectToServer(cluster,
dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
* output, so we have to copy that system table. It's easiest to do that
* by treating it as a user table.
*/
- snprintf(query + strlen(query), sizeof(query) - strlen(query),
- "WITH regular_heap (reloid, indtable, toastheap) AS ( "
- " SELECT c.oid, 0::oid, 0::oid "
- " FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
- " ON c.relnamespace = n.oid "
- " WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
- CppAsString2(RELKIND_MATVIEW) ") AND "
- /* exclude possible orphaned temp tables */
- " ((n.nspname !~ '^pg_temp_' AND "
- " n.nspname !~ '^pg_toast_temp_' AND "
- " n.nspname NOT IN ('pg_catalog', 'information_schema', "
- " 'binary_upgrade', 'pg_toast') AND "
- " c.oid >= %u::pg_catalog.oid) OR "
- " (n.nspname = 'pg_catalog' AND "
- " relname IN ('pg_largeobject') ))), ",
- FirstNormalObjectId);
+ if (skip_gtt)
+ {
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
+ "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+ " SELECT c.oid, 0::oid, 0::oid "
+ " FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+ " ON c.relnamespace = n.oid "
+ " WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+ CppAsString2(RELKIND_MATVIEW) ") AND "
+ /* exclude global temp tables */
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+ /* exclude possible orphaned temp tables */
+ " ((n.nspname !~ '^pg_temp_' AND "
+ " n.nspname !~ '^pg_toast_temp_' AND "
+ " n.nspname NOT IN ('pg_catalog', 'information_schema', "
+ " 'binary_upgrade', 'pg_toast') AND "
+ " c.oid >= %u::pg_catalog.oid) OR "
+ " (n.nspname = 'pg_catalog' AND "
+ " relname IN ('pg_largeobject') ))), ",
+ FirstNormalObjectId);
+ }
+ else
+ {
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
+ "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+ " SELECT c.oid, 0::oid, 0::oid "
+ " FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+ " ON c.relnamespace = n.oid "
+ " WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+ CppAsString2(RELKIND_MATVIEW) ") AND "
+ /* exclude possible orphaned temp tables */
+ " ((n.nspname !~ '^pg_temp_' AND "
+ " n.nspname !~ '^pg_toast_temp_' AND "
+ " n.nspname NOT IN ('pg_catalog', 'information_schema', "
+ " 'binary_upgrade', 'pg_toast') AND "
+ " c.oid >= %u::pg_catalog.oid) OR "
+ " (n.nspname = 'pg_catalog' AND "
+ " relname IN ('pg_largeobject') ))), ",
+ FirstNormalObjectId);
+ }
/*
* Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
set_frozenxids(true);
/* update new_cluster info now that we have objects in the databases */
- get_db_and_rel_infos(&new_cluster);
+ get_db_and_rel_infos(&new_cluster, true);
}
/*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
"UPDATE pg_catalog.pg_class "
"SET relfrozenxid = '%u' "
/* only heap, materialized view, and TOAST are vacuumed */
- "WHERE relkind IN ("
+ "WHERE "
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+ "relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
"UPDATE pg_catalog.pg_class "
"SET relminmxid = '%u' "
/* only heap, materialized view, and TOAST are vacuumed */
- "WHERE relkind IN ("
+ "WHERE "
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+ "relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void check_loadable_libraries(void);
FileNameMap *gen_db_file_maps(DbInfo *old_db,
DbInfo *new_db, int *nmaps, const char *old_pgdata,
const char *new_pgdata);
-void get_db_and_rel_infos(ClusterInfo *cluster);
+void get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
void print_maps(FileNameMap *maps, int n,
const char *db_name);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
if (pset.sversion >= 90100)
{
appendPQExpBuffer(&buf,
- ",\n CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+ ",\n CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+ gettext_noop("session"),
gettext_noop("permanent"),
gettext_noop("temporary"),
gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b6b08d0..16377e6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1016,6 +1016,8 @@ static const pgsql_thing_t words_after_create[] = {
{"FOREIGN TABLE", NULL, NULL, NULL},
{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
{"GROUP", Query_for_list_of_roles},
+ {"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+ * ... */
{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
{"LANGUAGE", Query_for_list_of_languages},
{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2378,6 +2380,9 @@ psql_completion(const char *text, int start, int end)
/* CREATE FOREIGN DATA WRAPPER */
else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+ /* CREATE GLOBAL TEMP/TEMPORARY*/
+ else if (Matches("CREATE", "GLOBAL"))
+ COMPLETE_WITH("TEMP", "TEMPORARY");
/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2586,6 +2591,8 @@ psql_completion(const char *text, int start, int end)
/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+ else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+ COMPLETE_WITH("TABLE");
/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
else if (TailMatches("CREATE", "UNLOGGED"))
COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
#define RELPERSISTENCE_PERMANENT 'p' /* regular table */
#define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */
#define RELPERSISTENCE_TEMP 't' /* temporary table */
+#define RELPERSISTENCE_GLOBAL_TEMP 'g' /* global temporary table */
/* default selection for replica identity (primary key or nothing) */
#define REPLICA_IDENTITY_DEFAULT 'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f..6f8591c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5507,6 +5507,40 @@
proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
prosrc => 'pg_stat_get_xact_function_self_time' },
+# For global temporary table
+{ oid => '4191',
+ descr => 'List local statistics for global temporary table',
+ proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+ proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+ proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+ proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+ prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+ descr => 'List local relstats for global temporary table',
+ proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+ proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+ proargmodes => '{i,o,o,o,o,o}',
+ proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+ prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+ descr => 'List attached pid for one global temporary table',
+ proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+ proallargtypes => '{oid,oid,int4}',
+ proargmodes => '{i,o,o}',
+ proargnames => '{relid,relid,pid}',
+ prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+ descr => 'List those backends that have used global temporary table',
+ proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+ proallargtypes => '{int4,xid}',
+ proargmodes => '{o,o}',
+ proargnames => '{pid,relfrozenxid}',
+ prosrc => 'pg_list_gtt_relfrozenxids' },
+
{ oid => '3788',
descr => 'statistics: timestamp of the current statistics snapshot',
proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
#include "storage/smgr.h"
#include "utils/relcache.h"
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
extern void RelationDropStorage(Relation rel);
extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..63f7241
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,43 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ * prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, bool isCommit);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+
+#endif /* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
extern const char *seq_identify(uint8 info);
extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
#endif /* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid attnumTypeId(Relation rd, int attid);
extern Oid attnumCollationId(Relation rd, int attid);
extern bool isQueryUsingTempRelation(Query *query);
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
#endif /* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
#define PageClearPrunable(page) \
(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+ ((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
/* ----------------------------------------------------------------
* extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_TBM,
LWTRANCHE_PARALLEL_APPEND,
LWTRANCHE_SXACT,
+ LWTRANCHE_GTT_CTL,
LWTRANCHE_FIRST_USER_DEFINED
} BuiltinTrancheIds;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
Oid tempNamespaceId; /* OID of temp schema this backend is
* using */
+ TransactionId session_gtt_frozenxid; /* session level global temp table relfrozenxid */
+
bool isBackgroundWorker; /* true if background worker. */
/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
TransactionId *catalog_xmin);
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
#endif /* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int tcp_user_timeout;
extern bool trace_sort;
#endif
+/* global temporary table */
+extern int max_active_gtt;
+/* end */
+
/*
* Functions exported by guc.c
*/
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..6b6d2da 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
struct SMgrRelationData *rd_smgr; /* cached file handle, or NULL */
int rd_refcnt; /* reference count */
BackendId rd_backend; /* owning backend id, if temporary relation */
- bool rd_islocaltemp; /* rel is a temp rel of this session */
+ bool rd_islocaltemp; /* rel is a temp rel of this session */
bool rd_isnailed; /* rel is nailed in cache */
bool rd_isvalid; /* relcache entry is valid */
bool rd_indexvalid; /* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
int parallel_workers; /* max number of parallel workers */
bool vacuum_index_cleanup; /* enables index vacuuming and cleanup */
bool vacuum_truncate; /* enables vacuum to truncate a relation */
+ bool on_commit_delete_rows; /* global temp table */
} StdRdOptions;
#define HEAP_MIN_FILLFACTOR 10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
* True if relation's pages are stored in local buffers.
*/
#define RelationUsesLocalBuffers(relation) \
- ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
/*
* RELATION_IS_LOCAL
- * If a rel is either temp or newly created in the current transaction,
+ * If a rel is either local temp or global temp relation
+ * or newly created in the current transaction,
* it can be assumed to be accessible only to the current backend.
* This is typically used to decide that we can skip acquiring locks.
*
@@ -547,6 +550,7 @@ typedef struct ViewOptions
*/
#define RELATION_IS_LOCAL(relation) \
((relation)->rd_islocaltemp || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
(relation)->rd_createSubid != InvalidSubTransactionId)
/*
@@ -559,6 +563,14 @@ typedef struct ViewOptions
((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
!(relation)->rd_islocaltemp)
+/*
+ * RELATION_IS_TEMP
+ * Test a rel is either local temp relation of this session
+ * or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+ ((relation)->rd_islocaltemp || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
/*
* RelationIsScannable
@@ -602,6 +614,17 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation) ((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation) \
+ ((relation)->rd_options && \
+ ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+ ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..73d3d7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,8 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE: drop cascades to 5 other objects
+DETAIL: drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..067393b
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,322 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+ product_no integer PRIMARY KEY,
+ name text,
+ price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR: cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR: not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR: ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR: not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR: The global temp table does not allow modification on commit action.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR: The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ok
+CREATE global temp TABLE measurement (
+ logdate date not null,
+ peaktemp int,
+ unitsales int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count
+-------
+ 2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count
+-------
+ 0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR: The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname;
+ relname | relkind | relpersistence | reloptions
+---------------------------------+---------+----------------+-------------------------------
+ p_table01 | p | g | {on_commit_delete_rows=true}
+ p_table01_2017 | r | g | {on_commit_delete_rows=true}
+ p_table01_2018 | r | g | {on_commit_delete_rows=true}
+ p_table01_id_seq | S | g |
+ p_table02 | p | g | {on_commit_delete_rows=false}
+ p_table02_2017 | r | g | {on_commit_delete_rows=false}
+ p_table02_2018 | r | g | {on_commit_delete_rows=false}
+ p_table02_id_seq | S | g |
+ tbl_inherits_parent | r | p |
+ tbl_inherits_parent_global_temp | r | g | {on_commit_delete_rows=true}
+ tbl_inherits_partition | r | g | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR: global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR: can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+ERROR: constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR: insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL: Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count
+-------
+ 1
+(1 row)
+
+select count(*) from orders;
+ count
+-------
+ 0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+ id SERIAL PRIMARY KEY,
+ data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a
+----+---
+ 2 | 1
+ 3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a
+----+---
+ 4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR: materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on idx_gt1_1
+ Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: (a = 200000)
+ -> Bitmap Index Scan on idx_gt1_1
+ Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: ((a * 10) = 300)
+ -> Bitmap Index Scan on idx_gt1_3
+ Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: ((a * 10) = 3)
+ -> Bitmap Index Scan on idx_gt1_3
+ Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+ QUERY PLAN
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+ Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+ QUERY PLAN
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+ Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+ QUERY PLAN
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+ Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+ QUERY PLAN
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+ Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE: drop cascades to 19 other objects
+DETAIL: drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval
+---------
+ 1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..b2ef23b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,168 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+ QUERY PLAN
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+ Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+ relname | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon | 450560 | 8192 | 499712 | 114688 | 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+ relname
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+ relname | pg_relation_size | pg_table_size | pg_total_relation_size
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 | 65536 | 65536 | 65536
+ idx_gtt_t_kenyon_2 | 49152 | 49152 | 49152
+(2 rows)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2
+----+----
+ 1 | 1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..0e21b30
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible
+------------+-----------+----------+-----------+---------------
+ gtt_stats | gtt | 0 | 0 | 0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible
+------------+-----------+----------+-----------+---------------
+ gtt_stats | gtt | 55 | 10000 | 0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats | gtt | a | f | 0 | 4 | -1 | | | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} | 1 | | |
+ gtt_stats | gtt | b | f | 0 | 5 | 1 | {test} | {1} | | 1 | | |
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE: drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index c730461..8a39594 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
FROM pg_authid
WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.relid,
+ s.pid
+ FROM (pg_class c
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.relpages,
+ s.reltuples,
+ s.relallvisible,
+ s.relfrozenxid,
+ s.relminmxid
+ FROM (pg_class c
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN (s.stakind1 = 1) THEN s.stavalues1
+ WHEN (s.stakind2 = 1) THEN s.stavalues2
+ WHEN (s.stakind3 = 1) THEN s.stavalues3
+ WHEN (s.stakind4 = 1) THEN s.stavalues4
+ WHEN (s.stakind5 = 1) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS most_common_vals,
+ CASE
+ WHEN (s.stakind1 = 1) THEN s.stanumbers1
+ WHEN (s.stakind2 = 1) THEN s.stanumbers2
+ WHEN (s.stakind3 = 1) THEN s.stanumbers3
+ WHEN (s.stakind4 = 1) THEN s.stanumbers4
+ WHEN (s.stakind5 = 1) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS most_common_freqs,
+ CASE
+ WHEN (s.stakind1 = 2) THEN s.stavalues1
+ WHEN (s.stakind2 = 2) THEN s.stavalues2
+ WHEN (s.stakind3 = 2) THEN s.stavalues3
+ WHEN (s.stakind4 = 2) THEN s.stavalues4
+ WHEN (s.stakind5 = 2) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS histogram_bounds,
+ CASE
+ WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+ WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+ WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+ WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+ WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+ ELSE NULL::real
+ END AS correlation,
+ CASE
+ WHEN (s.stakind1 = 4) THEN s.stavalues1
+ WHEN (s.stakind2 = 4) THEN s.stavalues2
+ WHEN (s.stakind3 = 4) THEN s.stavalues3
+ WHEN (s.stakind4 = 4) THEN s.stavalues4
+ WHEN (s.stakind5 = 4) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS most_common_elems,
+ CASE
+ WHEN (s.stakind1 = 4) THEN s.stanumbers1
+ WHEN (s.stakind2 = 4) THEN s.stanumbers2
+ WHEN (s.stakind3 = 4) THEN s.stanumbers3
+ WHEN (s.stakind4 = 4) THEN s.stanumbers4
+ WHEN (s.stakind5 = 4) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN (s.stakind1 = 5) THEN s.stanumbers1
+ WHEN (s.stakind2 = 5) THEN s.stanumbers2
+ WHEN (s.stakind3 = 5) THEN s.stanumbers3
+ WHEN (s.stakind4 = 5) THEN s.stanumbers4
+ WHEN (s.stakind5 = 5) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS elem_count_histogram
+ FROM ((pg_class c
+ JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
pg_hba_file_rules| SELECT a.line_number,
a.type,
a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
# run stats by itself because its delay may be insufficient under heavy load
test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..c32c0bd
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,217 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+ product_no integer PRIMARY KEY,
+ name text,
+ price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+ logdate date not null,
+ peaktemp int,
+ unitsales int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+ id SERIAL PRIMARY KEY,
+ data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..bf0b922
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,79 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ {
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ }
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+ int natts = 0;
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ elog(ERROR, "backend %d relid %u already exists in global temporary table local hash",
+ MyBackendId, relid);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry->spcnode = rnode.spcNode;
+ entry->relpages = 0;
+ entry->reltuples = 0;
+ entry->relallvisible = 0;
+ entry->relfrozenxid = InvalidTransactionId;
+ entry->relminmxid = InvalidMultiXactId;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+ }
+
+ entry->relfrozenxid = RecentXmin;
+ entry->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(rnode);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, NULL);
+
+ if (entry)
+ {
+ int i;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(entry->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(entry->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ RelFileNode rnode;
+ rnode.spcNode = entry->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = entry->relid;
+ gtt_storage_checkout(rnode, false, isCommit);
+ }
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->att_stat_tups[i])
+ {
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ }
+ }
+
+ if (entry->attnum)
+ pfree(entry->attnum);
+
+ if (entry->att_stat_tups)
+ pfree(entry->att_stat_tups);
+ }
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, &found);
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ int nrels = 0,
+ maxrels = 0;
+ SMgrRelation *srels = NULL;
+ RelFileNode *rnodes = NULL;
+ char *relkinds = NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+
+ rnode.spcNode = entry->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = entry->relid;
+
+ srel = smgropen(rnode, MyBackendId);
+
+ /* allocate the initial array, or extend it, if needed */
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxrels);
+ rnodes = palloc(sizeof(RelFileNode) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+ rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ srels[nrels] = srel;
+ rnodes[nrels] = rnode;
+ relkinds[nrels] = entry->relkind;
+ nrels++;
+ }
+
+ if (nrels > 0)
+ {
+ int i;
+
+ smgrdounlinkall(srels, nrels, false);
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(rnodes[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ /* todo */
+ if (entry->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ Assert(entry->relid == reloid);
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == 0)
+ {
+ entry->attnum[i] = attnum;
+ break;
+ }
+ else if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < entry->natts);
+ Assert(entry->att_stat_tups[i] == NULL);
+ entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return NULL;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ return entry->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+ MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ HeapTuple tuple;
+ Relation rel = NULL;
+ int attnum = PG_GETARG_INT32(1);
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Tuplestorestate *tupstore;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ tuple = get_gtt_att_statistic(reloid, attnum, false);
+ if (tuple)
+ {
+ Datum values[31];
+ bool isnull[31];
+ HeapTuple res = NULL;
+
+ memset(&values, 0, sizeof(values));
+ memset(&isnull, 0, sizeof(isnull));
+ heap_deform_tuple(tuple, sd, values, isnull);
+ res = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, res);
+ }
+
+ table_close(rel, NoLock);
+ table_close(pg_tatistic, AccessShareLock);
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ if (get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid))
+ {
+ Datum values[5];
+ bool isnull[5];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(relpages);
+ values[1] = Float4GetDatum((float4)reltuples);
+ values[2] = Int32GetDatum(relallvisible);
+ values[3] = UInt32GetDatum(relfrozenxid);
+ values[4] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ map = copy_active_gtt_bitmap(rel->rd_node);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ int i = 0;
+ int j = 0;
+ uint32 oldest = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ if (max_active_gtt <= 0)
+ return (Datum) 0;
+
+ if (RecoveryInProgress())
+ return (Datum) 0;
+
+ pids = palloc0(sizeof(int) * num_xid);
+ xids = palloc0(sizeof(int) * num_xid);
+ oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+ if (i > 0)
+ {
+ if (i > 0)
+ {
+ pids[i] = 0;
+ xids[i] = oldest;
+ i++;
+ }
+
+ for(j = 0; j < i; j++)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(pids[j]);
+ values[1] = UInt32GetDatum(xids[j]);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ }
+ tuplestore_donestoring(tupstore);
+ pfree(pids);
+ pfree(xids);
+
+ return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(indexOid));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid relOid = index->rd_index->indrelid;
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ if (gtt_storage_attached(relOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ int i;
+ Oid toastrelid;
+
+ if (operation != CMD_INSERT)
+ return;
+
+ if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+ return;
+
+ if (!RELATION_IS_GLOBAL_TEMP(relation))
+ return;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_attached(relation->rd_node.relNode))
+ return;
+
+ RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+ for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+ {
+ Relation index = resultRelInfo->ri_IndexRelationDescs[i];
+ IndexInfo *info = resultRelInfo->ri_IndexRelationInfo[i];
+
+ Assert(index->rd_index->indisvalid);
+ Assert(index->rd_index->indislive);
+ Assert(index->rd_index->indisready);
+
+ index_build(relation, index, info, true, false);
+ }
+
+ toastrelid = relation->rd_rel->reltoastrelid;
+ if (OidIsValid(toastrelid))
+ {
+ Relation toastrel;
+ ListCell *indlist;
+
+ toastrel = table_open(toastrelid, RowExclusiveLock);
+ RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+ foreach(indlist, RelationGetIndexList(toastrel))
+ {
+ Oid indexId = lfirst_oid(indlist);
+ Relation currentIndex;
+ IndexInfo *indexInfo;
+
+ currentIndex = index_open(indexId, RowExclusiveLock);
+
+ indexInfo = BuildDummyIndexInfo(currentIndex);
+ index_build(toastrel, currentIndex, indexInfo, true, false);
+ index_close(currentIndex, NoLock);
+ }
+
+ table_close(toastrel, NoLock);
+ }
+
+ return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46..491db16 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
WHERE NOT pg_is_other_temp_schema(N.oid)
AND relkind = 'S';
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stavalues1
+ WHEN s.stakind2 = 1 THEN s.stavalues2
+ WHEN s.stakind3 = 1 THEN s.stavalues3
+ WHEN s.stakind4 = 1 THEN s.stavalues4
+ WHEN s.stakind5 = 1 THEN s.stavalues5
+ END AS most_common_vals,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stanumbers1
+ WHEN s.stakind2 = 1 THEN s.stanumbers2
+ WHEN s.stakind3 = 1 THEN s.stanumbers3
+ WHEN s.stakind4 = 1 THEN s.stanumbers4
+ WHEN s.stakind5 = 1 THEN s.stanumbers5
+ END AS most_common_freqs,
+ CASE
+ WHEN s.stakind1 = 2 THEN s.stavalues1
+ WHEN s.stakind2 = 2 THEN s.stavalues2
+ WHEN s.stakind3 = 2 THEN s.stavalues3
+ WHEN s.stakind4 = 2 THEN s.stavalues4
+ WHEN s.stakind5 = 2 THEN s.stavalues5
+ END AS histogram_bounds,
+ CASE
+ WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+ WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+ WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+ WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+ WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+ END AS correlation,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stavalues1
+ WHEN s.stakind2 = 4 THEN s.stavalues2
+ WHEN s.stakind3 = 4 THEN s.stavalues3
+ WHEN s.stakind4 = 4 THEN s.stavalues4
+ WHEN s.stakind5 = 4 THEN s.stavalues5
+ END AS most_common_elems,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stanumbers1
+ WHEN s.stakind2 = 4 THEN s.stanumbers2
+ WHEN s.stakind3 = 4 THEN s.stanumbers3
+ WHEN s.stakind4 = 4 THEN s.stanumbers4
+ WHEN s.stakind5 = 4 THEN s.stanumbers5
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN s.stakind1 = 5 THEN s.stanumbers1
+ WHEN s.stakind2 = 5 THEN s.stanumbers2
+ WHEN s.stakind3 = 5 THEN s.stanumbers3
+ WHEN s.stakind4 = 5 THEN s.stanumbers4
+ WHEN s.stakind5 = 5 THEN s.stanumbers5
+ END AS elem_count_histogram
+ FROM
+ pg_class c
+ JOIN pg_attribute a ON c.oid = a.attrelid
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
CREATE VIEW pg_stats WITH (security_barrier) AS
SELECT
nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..7efc5a9 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
#include "commands/dbcommands.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int acquire_inherited_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
static void update_attstats(Oid relid, bool inh,
- int natts, VacAttrStats **vacattrstats);
+ int natts, VacAttrStats **vacattrstats, char relpersistence);
static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
return;
}
+ if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+ !gtt_storage_attached(onerel->rd_node.relNode))
+ {
+ relation_close(onerel, ShareUpdateExclusiveLock);
+ return;
+ }
+
/*
* We can ANALYZE any table except pg_statistic. See update_attstats
*/
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* pg_statistic for columns we didn't process, we leave them alone.)
*/
update_attstats(RelationGetRelid(onerel), inh,
- attr_cnt, vacattrstats);
+ attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
for (ind = 0; ind < nindexes; ind++)
{
AnlIndexData *thisdata = &indexdata[ind];
update_attstats(RelationGetRelid(Irel[ind]), false,
- thisdata->attr_cnt, thisdata->vacattrstats);
+ thisdata->attr_cnt, thisdata->vacattrstats,
+ RelationGetRelPersistence(Irel[ind]));
}
/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
* by taking a self-exclusive lock on the relation in analyze_rel().
*/
static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
{
Relation sd;
int attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
}
}
- /* Is there already a pg_statistic tuple for this attribute? */
- oldtup = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(relid),
- Int16GetDatum(stats->attr->attnum),
- BoolGetDatum(inh));
-
- if (HeapTupleIsValid(oldtup))
+ /* Update column statistic to localhash, not catalog */
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
{
- /* Yes, replace it */
- stup = heap_modify_tuple(oldtup,
- RelationGetDescr(sd),
- values,
- nulls,
- replaces);
- ReleaseSysCache(oldtup);
- CatalogTupleUpdate(sd, &stup->t_self, stup);
+ up_gtt_att_statistic(relid,
+ stats->attr->attnum,
+ inh,
+ natts,
+ RelationGetDescr(sd),
+ values,
+ nulls);
}
else
{
- /* No, insert new tuple */
- stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
- CatalogTupleInsert(sd, stup);
- }
+ /* Is there already a pg_statistic tuple for this attribute? */
+ oldtup = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(relid),
+ Int16GetDatum(stats->attr->attnum),
+ BoolGetDatum(inh));
- heap_freetuple(stup);
+ if (HeapTupleIsValid(oldtup))
+ {
+ /* Yes, replace it */
+ stup = heap_modify_tuple(oldtup,
+ RelationGetDescr(sd),
+ values,
+ nulls,
+ replaces);
+ ReleaseSysCache(oldtup);
+ CatalogTupleUpdate(sd, &stup->t_self, stup);
+ }
+ else
+ {
+ /* No, insert new tuple */
+ stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+ CatalogTupleInsert(sd, stup);
+ }
+
+ heap_freetuple(stup);
+ }
}
table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
errmsg("cannot vacuum temporary tables of other sessions")));
}
+ /* not support cluster global temp table yet */
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("not support cluster global temporary tables yet")));
+
/*
* Also check for active uses of the relation in the current transaction,
* including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e79ede4..078694d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
#include "catalog/dependency.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
#include "commands/copy.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
@@ -1062,7 +1063,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
Assert(rel);
/* check read-only transaction and parallel mode */
- if (XactReadOnly && !rel->rd_islocaltemp)
+ if (XactReadOnly && !RELATION_IS_TEMP(rel))
PreventCommandIfReadOnly("COPY FROM");
cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2840,6 +2841,8 @@ CopyFrom(CopyState cstate)
ExecOpenIndices(resultRelInfo, false);
+ init_gtt_storage(CMD_INSERT, resultRelInfo);
+
estate->es_result_relations = resultRelInfo;
estate->es_num_result_relations = 1;
estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 3f3a89f..928c01e 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2586,6 +2586,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
!isTempNamespace(classtuple->relnamespace))
continue;
+ /* not support reindex on global temp table, so skip it */
+ if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ ereport(WARNING,
+ (errmsg("global temp table \"%s.%s\" skip reindexed",
+ get_namespace_name(get_rel_namespace(relid)),
+ get_rel_name(relid))));
+ continue;
+ }
+
/* Check user/system classification, and optionally skip */
if (objectKind == REINDEX_OBJECT_SYSTEM &&
!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..deecde8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* transaction.
*/
relpersistence = get_rel_persistence(relid);
- if (relpersistence == RELPERSISTENCE_TEMP)
+ if (relpersistence == RELPERSISTENCE_TEMP ||
+ relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..fada1ac 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
#include "catalog/objectaccess.h"
#include "catalog/pg_sequence.h"
#include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
#include "commands/defrem.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
*/
static SeqTableData *last_used_seq = NULL;
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
static Relation lock_and_open_sequence(SeqTable seq);
static void create_seq_hashtable(void);
static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
List **owned_by);
static void do_setval(Oid relid, int64 next, bool iscalled);
static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
/*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
/* now initialize the sequence's data */
tuple = heap_form_tuple(tupDesc, value, null);
- fill_seq_with_data(rel, tuple);
+ fill_seq_with_data(rel, tuple, InvalidBuffer);
/* process OWNED BY if given */
if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
/*
* Insert the modified tuple into the new storage file.
*/
- fill_seq_with_data(seq_rel, tuple);
+ fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
/* Clear local cache so that we don't think we have cached numbers */
/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
* Initialize a sequence's relation with the specified tuple as content
*/
static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
{
- Buffer buf;
Page page;
sequence_magic *sm;
OffsetNumber offnum;
+ bool lockBuffer = false;
/* Initialize first page of relation with special magic number */
- buf = ReadBuffer(rel, P_NEW);
- Assert(BufferGetBlockNumber(buf) == 0);
+ if (buf == InvalidBuffer)
+ {
+ buf = ReadBuffer(rel, P_NEW);
+ Assert(BufferGetBlockNumber(buf) == 0);
+ lockBuffer = true;
+ }
page = BufferGetPage(buf);
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
/* Now insert sequence tuple */
- LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+ if (lockBuffer)
+ LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
/*
* Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
END_CRIT_SECTION();
- UnlockReleaseBuffer(buf);
+ if (lockBuffer)
+ UnlockReleaseBuffer(buf);
}
/*
@@ -502,7 +511,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
/*
* Insert the modified tuple into the new storage file.
*/
- fill_seq_with_data(seqrel, newdatatuple);
+ fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
}
/* process OWNED BY if given */
@@ -611,7 +620,7 @@ nextval_internal(Oid relid, bool check_permissions)
RelationGetRelationName(seqrel))));
/* read-only transactions may only modify temp sequences */
- if (!seqrel->rd_islocaltemp)
+ if (!RELATION_IS_TEMP(seqrel))
PreventCommandIfReadOnly("nextval()");
/*
@@ -936,7 +945,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
ReleaseSysCache(pgstuple);
/* read-only transactions may only modify temp sequences */
- if (!seqrel->rd_islocaltemp)
+ if (!RELATION_IS_TEMP(seqrel))
PreventCommandIfReadOnly("setval()");
/*
@@ -1154,6 +1163,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
/* Return results */
*p_elm = elm;
*p_rel = seqrel;
+
+ if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+ !gtt_storage_attached(seqrel->rd_node.relNode))
+ {
+ RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+ gtt_init_seq(seqrel);
+ }
}
@@ -1954,3 +1970,46 @@ seq_mask(char *page, BlockNumber blkno)
mask_unused_space(page);
}
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+ HeapTuple seqtuple;
+ Form_pg_sequence seqform;
+ int64 start;
+
+ seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+ if (!HeapTupleIsValid(seqtuple))
+ elog(ERROR, "cache lookup failed for sequence %u",
+ seqid);
+
+ seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+ start = seqform->seqstart;
+ ReleaseSysCache(seqtuple);
+
+ return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+ /* Initialize sequence for global temporary tables */
+ Datum value[SEQ_COL_LASTCOL] = {0};
+ bool null[SEQ_COL_LASTCOL] = {false};
+ HeapTuple tuple;
+ int64 startv = get_seqence_start_value(RelationGetRelid(rel));
+
+ /*
+ * last_value from pg_sequence.seqstart
+ * log_cnt = 0
+ * is_called = false
+ */
+ value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+ tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+ fill_seq_with_data(rel, tuple, InvalidBuffer);
+ heap_freetuple(tuple);
+
+ return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7a13b97..536083e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
#include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
#include "commands/cluster.h"
#include "commands/comment.h"
#include "commands/defrem.h"
@@ -553,6 +554,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
Relation partitionTbl);
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
/* ----------------------------------------------------------------
@@ -598,6 +600,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
LOCKMODE parentLockmode;
const char *accessMethod = NULL;
Oid accessMethodId = InvalidOid;
+ OnCommitAction oncommit_action = ONCOMMIT_NOOP;
/*
* Truncate relname to appropriate length (probably a waste of time, as
@@ -608,8 +611,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Check consistency of arguments
*/
+ /* global temp table same as local temp table */
if (stmt->oncommit != ONCOMMIT_NOOP
- && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+ && !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+ stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
@@ -639,7 +644,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* code. This is needed because calling code might not expect untrusted
* tables to appear in pg_temp at the front of its search path.
*/
- if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+ /* global temp table same as local temp table */
+ if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+ stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
&& InSecurityRestrictedOperation())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -740,6 +747,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Parse and validate reloptions, if any.
*/
+ /* global temp table */
+ oncommit_action = gtt_oncommit_option(stmt->options);
+ if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+ {
+ /* check parent table*/
+ if (inheritOids)
+ {
+ Oid parent = linitial_oid(inheritOids);
+ Relation relation = table_open(parent, NoLock);
+
+ if (!RELATION_IS_GLOBAL_TEMP(relation))
+ elog(ERROR, "The parent table must be global temporary table");
+
+ table_close(relation, NoLock);
+ }
+
+ if (oncommit_action != ONCOMMIT_NOOP)
+ {
+ if (stmt->oncommit != ONCOMMIT_NOOP)
+ elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+
+ stmt->oncommit = oncommit_action;
+ }
+ else
+ {
+ DefElem *opt = makeNode(DefElem);
+
+ opt->type = T_DefElem;
+ opt->defnamespace = NULL;
+ opt->defname = "on_commit_delete_rows";
+ opt->defaction = DEFELEM_UNSPEC;
+
+ /* use reloptions to remember on commit clause */
+ if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+ opt->arg = (Node *)makeString("true");
+ else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+ opt->arg = (Node *)makeString("false");
+ else if (stmt->oncommit == ONCOMMIT_NOOP)
+ opt->arg = (Node *)makeString("false");
+ else
+ elog(ERROR, "global temp table not support on commit drop clause");
+
+ stmt->options = lappend(stmt->options, opt);
+ }
+ }
+ else if (oncommit_action != ONCOMMIT_NOOP)
+ elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
true, false);
@@ -1824,7 +1880,8 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
* table or the current physical file to be thrown away anyway.
*/
if (rel->rd_createSubid == mySubid ||
- rel->rd_newRelfilenodeSubid == mySubid)
+ rel->rd_newRelfilenodeSubid == mySubid ||
+ RELATION_IS_GLOBAL_TEMP(rel))
{
/* Immediate, non-rollbackable truncation is OK */
heap_truncate_one_rel(rel);
@@ -3567,6 +3624,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
/* Caller is required to provide an adequate lock. */
rel = relation_open(context->relid, NoLock);
+ /* We allow to alter global temp table only this session use it */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (is_other_backend_use_gtt(rel->rd_node))
+ elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+ RelationGetRelationName(rel));
+ }
+
CheckTableNotInUse(rel, "ALTER TABLE");
ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8179,6 +8244,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("constraints on temporary tables must involve temporary tables of this session")));
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("constraints on global temporary tables may reference only global temporary tables")));
+ break;
}
/*
@@ -12647,6 +12718,12 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
if (defList == NIL && operation != AT_ReplaceRelOptions)
return; /* nothing to do */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+ elog(ERROR, "The global temp table does not allow modification on commit action.");
+ }
+
pgclass = table_open(RelationRelationId, RowExclusiveLock);
/* Fetch heap tuple */
@@ -12849,6 +12926,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
*/
rel = relation_open(tableOid, lockmode);
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ elog(ERROR, "not support alter table set tablespace on global temp table");
+
/*
* No work if no change in tablespace.
*/
@@ -13221,7 +13301,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
* NOTE: any conflict in relfilenode value will be caught in
* RelationCreateStorage().
*/
- RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+ RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
/* copy main fork */
RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14628,7 +14708,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
*/
switch (rel->rd_rel->relpersistence)
{
+ /* global temp table same as local temp table */
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_GLOBAL_TEMP:
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17220,3 +17302,28 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
table_close(rel, NoLock);
}
}
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+ ListCell *listptr;
+ OnCommitAction action = ONCOMMIT_NOOP;
+
+ foreach(listptr, options)
+ {
+ DefElem *def = (DefElem *) lfirst(listptr);
+
+ if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+ {
+ if (defGetBoolean(def))
+ action = ONCOMMIT_DELETE_ROWS;
+ else
+ action = ONCOMMIT_PRESERVE_ROWS;
+
+ break;
+ }
+ }
+
+ return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..f58c44d 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
HeapTuple ctup;
Form_pg_class pgcform;
bool dirty;
+ bool is_gtt = false;
+
+ /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+ if (RELATION_IS_GLOBAL_TEMP(relation))
+ {
+ is_gtt = true;
+ up_gtt_relstats(relation,
+ num_pages, num_tuples,
+ num_all_visible_pages,
+ frozenxid, minmulti);
+ }
rd = table_open(RelationRelationId, RowExclusiveLock);
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
/* Apply statistical updates, if any, to copied tuple */
dirty = false;
- if (pgcform->relpages != (int32) num_pages)
+
+ if (is_gtt)
+ relation->rd_rel->relpages = (int32) num_pages;
+ else if (pgcform->relpages != (int32) num_pages)
{
pgcform->relpages = (int32) num_pages;
dirty = true;
}
- if (pgcform->reltuples != (float4) num_tuples)
+
+ if (is_gtt)
+ relation->rd_rel->reltuples = (float4) num_tuples;
+ else if (pgcform->reltuples != (float4) num_tuples)
{
pgcform->reltuples = (float4) num_tuples;
dirty = true;
}
- if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+ if (is_gtt)
+ relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+ else if (pgcform->relallvisible != (int32) num_all_visible_pages)
{
pgcform->relallvisible = (int32) num_all_visible_pages;
dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
* This should match vac_update_datfrozenxid() concerning what we consider
* to be "in the future".
*/
- if (TransactionIdIsNormal(frozenxid) &&
+ if (!is_gtt &&
+ TransactionIdIsNormal(frozenxid) &&
pgcform->relfrozenxid != frozenxid &&
(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
}
/* Similarly for relminmxid */
- if (MultiXactIdIsValid(minmulti) &&
+ if (!is_gtt &&
+ MultiXactIdIsValid(minmulti) &&
pgcform->relminmxid != minmulti &&
(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
continue;
}
+ /* global temp table relstats not in pg_class */
+ if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+
/*
* Some table AMs might not need per-relation xid / multixid horizons.
* It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
Assert(TransactionIdIsNormal(newFrozenXid));
Assert(MultiXactIdIsValid(newMinMulti));
+ /*
+ * Global temp table get frozenxid from MyProc
+ * to avoid the vacuum truncate clog that gtt need.
+ */
+ if (max_active_gtt > 0)
+ {
+ TransactionId oldest_gtt_frozenxid =
+ list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+ if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+ TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+ {
+ ereport(WARNING,
+ (errmsg("global temp table oldest FrozenXid is far in the past"),
+ errhint("please truncate them or kill those sessions that use them.")));
+ newFrozenXid = oldest_gtt_frozenxid;
+ }
+ }
+
/* Now fetch the pg_database tuple we need to update. */
relation = table_open(DatabaseRelationId, RowExclusiveLock);
@@ -1778,6 +1824,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return false;
}
+ if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+ !gtt_storage_attached(onerel->rd_node.relNode))
+ {
+ ereport(WARNING,
+ (errmsg("skipping vacuum empty global temp table \"%s\"",
+ RelationGetRelationName(onerel))));
+ relation_close(onerel, lmode);
+ PopActiveSnapshot();
+ CommitTransactionCommand();
+ return false;
+ }
+
/*
* Silently ignore tables that are temp tables of other backends ---
* trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 28130fb..dcb4a0b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
if (isTempNamespace(get_rel_namespace(rte->relid)))
continue;
+ if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+
PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
}
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index c13b1d3..253054d 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
#include "foreign/fdwapi.h"
@@ -543,6 +544,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
(node != NULL &&
node->onConflictAction != ONCONFLICT_NONE));
+ init_gtt_storage(mtstate->operation, leaf_part_rri);
+
/*
* Build WITH CHECK OPTION constraints for the partition. Note that we
* didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
#include "access/tableam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
#include "commands/trigger.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecOpenIndices(resultRelInfo,
node->onConflictAction != ONCONFLICT_NONE);
+ init_gtt_storage(operation, resultRelInfo);
+
/*
* If this is an UPDATE and a BEFORE UPDATE trigger is present, the
* trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte)
{
+ char relpersistence;
+
/*
* The flag has previously been initialized to false, so we can just
* return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* the rest of the necessary infrastructure right now anyway. So
* for now, bail out if we see a temporary table.
*/
- if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+ /* global temp table is same as local temp table */
+ relpersistence = get_rel_persistence(rte->relid);
+
+ if (relpersistence == RELPERSISTENCE_TEMP ||
+ relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
return;
/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b44efd6..14dbaaf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
* Furthermore, any index predicate or index expressions must be parallel
* safe.
*/
+ /* global temp table is same as local temp table */
if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+ heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..93c6d97 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
#include "catalog/pg_am.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
continue;
}
+ /* Ignore empty index for global temp table */
+ if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+ !gtt_storage_attached(indexRelation->rd_node.relNode))
+ {
+ index_close(indexRelation, NoLock);
+ continue;
+ }
+
/*
* If the index is valid, but cannot yet be used, ignore it; but
* mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412..0685c1c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("materialized views must not use temporary tables or views")));
+ if (is_query_using_gtt(query))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialized views must not use global temporary tables or views")));
+
/*
* A materialized view would either need to save parameters for use in
* maintaining/loading the data or prohibit them entirely. The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 96e7fdb..30f585c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp: TEMPORARY { $$ = RELPERSISTENCE_TEMP; }
| LOCAL TEMP { $$ = RELPERSISTENCE_TEMP; }
| GLOBAL TEMPORARY
{
- ereport(WARNING,
- (errmsg("GLOBAL is deprecated in temporary table creation"),
- parser_errposition(@1)));
- $$ = RELPERSISTENCE_TEMP;
+ $$ = RELPERSISTENCE_GLOBAL_TEMP;
}
| GLOBAL TEMP
{
- ereport(WARNING,
- (errmsg("GLOBAL is deprecated in temporary table creation"),
- parser_errposition(@1)));
- $$ = RELPERSISTENCE_TEMP;
+ $$ = RELPERSISTENCE_GLOBAL_TEMP;
}
| UNLOGGED { $$ = RELPERSISTENCE_UNLOGGED; }
| /*EMPTY*/ { $$ = RELPERSISTENCE_PERMANENT; }
@@ -11594,19 +11588,13 @@ OptTempTableName:
}
| GLOBAL TEMPORARY opt_table qualified_name
{
- ereport(WARNING,
- (errmsg("GLOBAL is deprecated in temporary table creation"),
- parser_errposition(@1)));
$$ = $4;
- $$->relpersistence = RELPERSISTENCE_TEMP;
+ $$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
}
| GLOBAL TEMP opt_table qualified_name
{
- ereport(WARNING,
- (errmsg("GLOBAL is deprecated in temporary table creation"),
- parser_errposition(@1)));
$$ = $4;
- $$->relpersistence = RELPERSISTENCE_TEMP;
+ $$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
}
| UNLOGGED opt_table qualified_name
{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
List **colnames, List **colvars);
static int specialAttNum(const char *attname);
static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
/*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
isQueryUsingTempRelation_walker,
context);
}
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Query))
+ {
+ Query *query = (Query *) node;
+ ListCell *rtable;
+
+ foreach(rtable, query->rtable)
+ {
+ RangeTblEntry *rte = lfirst(rtable);
+
+ if (rte->rtekind == RTE_RELATION)
+ {
+ Relation rel = relation_open(rte->relid, AccessShareLock);
+ char relpersistence = rel->rd_rel->relpersistence;
+
+ relation_close(rel, AccessShareLock);
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ return true;
+ }
+ }
+
+ return query_tree_walker(query,
+ is_query_using_gtt_walker,
+ context,
+ QTW_IGNORE_JOINALIASES);
+ }
+
+ return expression_tree_walker(node,
+ is_query_using_gtt_walker,
+ context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+ return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index af77f18..67d34f0 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
seqstmt->options = seqoptions;
+ if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
/*
* If a sequence data type was specified, add it to the options. Prepend
* to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index e3a43d3..ca460ca 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2087,6 +2087,11 @@ do_autovacuum(void)
}
continue;
}
+ else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* autovacuum skip vacuum global temp table */
+ continue;
+ }
/* Fetch reloptions and the pgstat entry for this table */
relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2153,7 +2158,9 @@ do_autovacuum(void)
/*
* We cannot safely process other backends' temp tables, so skip 'em.
*/
- if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+ /* autovacuum skip vacuum global temp table */
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP ||
+ classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
continue;
relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 5880054..98f7b43 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
#include "executor/instrument.h"
#include "lib/binaryheap.h"
#include "miscadmin.h"
@@ -52,6 +53,7 @@
#include "utils/rel.h"
#include "utils/resowner_private.h"
#include "utils/timestamp.h"
+#include "utils/guc.h"
/* Note: these two macros only work on shared buffers, not local ones! */
@@ -2809,6 +2811,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
BlockNumber
RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
{
+ /*
+ * When this backend not init gtt storage
+ * return 0
+ */
+ if (RELATION_IS_GLOBAL_TEMP(relation) &&
+ !gtt_storage_attached(relation->rd_node.relNode))
+ {
+ return 0;
+ }
+
switch (relation->rd_rel->relkind)
{
case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
#include "access/nbtree.h"
#include "access/subtrans.h"
#include "access/twophase.h"
+#include "catalog/storage_gtt.h"
#include "commands/async.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
size = add_size(size, BTreeShmemSize());
size = add_size(size, SyncScanShmemSize());
size = add_size(size, AsyncShmemSize());
+ size = add_size(size, active_gtt_shared_hash_size());
#ifdef EXEC_BACKEND
size = add_size(size, ShmemBackendArraySize());
#endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
SUBTRANSShmemInit();
MultiXactShmemInit();
InitBufferPool();
+ /* global temporary table */
+ active_gtt_shared_hash_init();
/*
* Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 4a5b26c..9754168 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
#include "utils/builtins.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
+#include "utils/guc.h"
#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var))))
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
LWLockRelease(ProcArrayLock);
}
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+ ProcArrayStruct *arrayP = procArray;
+ TransactionId result = InvalidTransactionId;
+ int index;
+ int flags = 0;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return 0;
+
+ if (max_size > 0)
+ {
+ Assert(pids);
+ Assert(xids);
+ Assert(n);
+ *n = 0;
+ }
+
+ if (max_active_gtt <= 0)
+ return InvalidTransactionId;
+
+ if (RecoveryInProgress())
+ return InvalidTransactionId;
+
+ flags |= PROC_IS_AUTOVACUUM;
+ flags |= PROC_IN_LOGICAL_DECODING;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+ if (max_size > 0 && max_size < arrayP->numProcs)
+ {
+ LWLockRelease(ProcArrayLock);
+ elog(ERROR, "list_all_gtt_frozenxids require more array");
+ }
+
+ for (index = 0; index < arrayP->numProcs; index++)
+ {
+ int pgprocno = arrayP->pgprocnos[index];
+ volatile PGPROC *proc = &allProcs[pgprocno];
+ volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+ if (pgxact->vacuumFlags & flags)
+ continue;
+
+ if (proc->databaseId == MyDatabaseId &&
+ TransactionIdIsNormal(proc->session_gtt_frozenxid))
+ {
+ if (result == InvalidTransactionId)
+ result = proc->session_gtt_frozenxid;
+ else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+ result = proc->session_gtt_frozenxid;
+
+ if (max_size > 0)
+ {
+ pids[i] = proc->pid;
+ xids[i] = proc->session_gtt_frozenxid;
+ i++;
+ }
+ }
+ }
+ LWLockRelease(ProcArrayLock);
+
+ if (max_size > 0)
+ *n = i;
+
+ return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index eb321f7..3893cef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->tempNamespaceId = InvalidOid;
+ MyProc->session_gtt_frozenxid = InvalidTransactionId; /* init session level gtt frozenxid */
MyProc->isBackgroundWorker = IsBackgroundWorker;
MyPgXact->delayChkpt = false;
MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->tempNamespaceId = InvalidOid;
+ MyProc->session_gtt_frozenxid = InvalidTransactionId; /* init session level gtt frozenxid */
MyProc->isBackgroundWorker = IsBackgroundWorker;
MyPgXact->delayChkpt = false;
MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
Assert(backend != InvalidBackendId);
}
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ backend = BackendIdForTempRelations();
+ break;
default:
elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0be26fe..c169c99 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
#include "executor/nodeAgg.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -4570,12 +4571,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
}
else if (index->indpred == NIL)
{
- vardata->statsTuple =
- SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(index->indexoid),
- Int16GetDatum(pos + 1),
- BoolGetDatum(false));
- vardata->freefunc = ReleaseSysCache;
+ char rel_persistence = get_rel_persistence(index->indexoid);
+
+ if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata->statsTuple =
+ get_gtt_att_statistic(index->indexoid,
+ Int16GetDatum(pos + 1),
+ false);
+ vardata->freefunc = release_gtt_statistic_cache;
+ }
+ else
+ {
+ vardata->statsTuple =
+ SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(index->indexoid),
+ Int16GetDatum(pos + 1),
+ BoolGetDatum(false));
+ vardata->freefunc = ReleaseSysCache;
+ }
if (HeapTupleIsValid(vardata->statsTuple))
{
@@ -4700,15 +4714,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
}
else if (rte->rtekind == RTE_RELATION)
{
- /*
- * Plain table or parent of an inheritance appendrel, so look up the
- * column in pg_statistic
- */
- vardata->statsTuple = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(rte->relid),
- Int16GetDatum(var->varattno),
- BoolGetDatum(rte->inh));
- vardata->freefunc = ReleaseSysCache;
+ char rel_persistence = get_rel_persistence(rte->relid);
+
+ if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+ var->varattno,
+ rte->inh);
+ vardata->freefunc = release_gtt_statistic_cache;
+ }
+ else
+ {
+ /*
+ * Plain table or parent of an inheritance appendrel, so look up the
+ * column in pg_statistic
+ */
+ vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(rte->relid),
+ Int16GetDatum(var->varattno),
+ BoolGetDatum(rte->inh));
+ vardata->freefunc = ReleaseSysCache;
+ }
if (HeapTupleIsValid(vardata->statsTuple))
{
@@ -6090,6 +6116,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
{
/* Simple variable --- look to stats for the underlying table */
RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+ char rel_persistence = get_rel_persistence(rte->relid);
Assert(rte->rtekind == RTE_RELATION);
relid = rte->relid;
@@ -6107,6 +6134,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata.statsTuple = get_gtt_att_statistic(relid,
+ colnum,
+ rte->inh);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6118,6 +6152,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
}
else
{
+ char rel_persistence = get_rel_persistence(index->indexoid);
+
/* Expression --- maybe there are stats for the index itself */
relid = index->indexoid;
colnum = 1;
@@ -6133,6 +6169,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata.statsTuple = get_gtt_att_statistic(relid,
+ colnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7046,6 +7089,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
/* attempt to lookup stats in relation for this index column */
if (attnum != 0)
{
+ char rel_persistence = get_rel_persistence(rte->relid);
+
/* Simple variable -- look to stats for the underlying table */
if (get_relation_stats_hook &&
(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7058,6 +7103,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
elog(ERROR,
"no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata.statsTuple =
+ get_gtt_att_statistic(rte->relid,
+ attnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple =
@@ -7070,6 +7123,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
}
else
{
+ char rel_persistence = get_rel_persistence(index->indexoid);
+
/*
* Looks like we've found an expression column in the index. Let's
* see if there's any stats for it.
@@ -7089,6 +7144,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ vardata.statsTuple =
+ get_gtt_att_statistic(index->indexoid,
+ attnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 3da90cb..97240ff 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
#include "catalog/pg_statistic.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
if (stawidth > 0)
return stawidth;
}
+ if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ tp = get_gtt_att_statistic(relid, attnum, false);
+ if (!HeapTupleIsValid(tp))
+ return 0;
+
+ stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+ if (stawidth > 0)
+ return stawidth;
+ else
+ return 0;
+ }
tp = SearchSysCache3(STATRELATTINH,
ObjectIdGetDatum(relid),
Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326..1cf7063 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
#include "catalog/pg_type.h"
#include "catalog/schemapg.h"
#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
#include "commands/policy.h"
#include "commands/trigger.h"
#include "miscadmin.h"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_islocaltemp = false;
}
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ {
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+
+ relation->rd_backend = BackendIdForTempRelations();
+ relation->rd_islocaltemp = false;
+ get_gtt_relstats(RelationGetRelid(relation),
+ &relpages,
+ &reltuples,
+ &relallvisible,
+ NULL, NULL);
+ relation->rd_rel->relpages = (int32)relpages;
+ relation->rd_rel->reltuples = (float4)reltuples;
+ relation->rd_rel->relallvisible = (int32)relallvisible;
+ }
+ break;
default:
elog(ERROR, "invalid relpersistence: %c",
relation->rd_rel->relpersistence);
@@ -1181,6 +1200,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
case RELKIND_PARTITIONED_INDEX:
Assert(relation->rd_rel->relam != InvalidOid);
RelationInitIndexAccessInfo(relation);
+ gtt_fix_index_state(relation);
break;
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
@@ -2220,6 +2240,8 @@ RelationReloadIndexInfo(Relation relation)
HeapTupleHeaderGetXmin(tuple->t_data));
ReleaseSysCache(tuple);
+
+ gtt_fix_index_state(relation);
}
/* Okay, now it's valid again */
@@ -3316,6 +3338,10 @@ RelationBuildLocalRelation(const char *relname,
rel->rd_backend = BackendIdForTempRelations();
rel->rd_islocaltemp = true;
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ rel->rd_backend = BackendIdForTempRelations();
+ rel->rd_islocaltemp = false;
+ break;
default:
elog(ERROR, "invalid relpersistence: %c", relpersistence);
break;
@@ -3430,6 +3456,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
TransactionId freezeXid = InvalidTransactionId;
RelFileNode newrnode;
+ if (RELATION_IS_GLOBAL_TEMP(relation))
+ elog(ERROR, "global temp table does not allow setting new relfilenode");
+
/* Allocate a new relfilenode */
newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
persistence);
@@ -3470,7 +3499,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
/* handle these directly, at least for now */
SMgrRelation srel;
- srel = RelationCreateStorage(newrnode, persistence);
+ srel = RelationCreateStorage(newrnode, persistence, relation);
smgrclose(srel);
}
break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index dbecc00..6acf3f3 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -140,6 +140,18 @@ char *GUC_check_errmsg_string;
char *GUC_check_errdetail_string;
char *GUC_check_errhint_string;
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define MIN_NUM_ACTIVE_GTT 0
+#define DEFAULT_NUM_ACTIVE_GTT 1000
+#define MAX_NUM_ACTIVE_GTT 1000000
+
+int max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2047,6 +2059,15 @@ static struct config_bool ConfigureNamesBool[] =
static struct config_int ConfigureNamesInt[] =
{
{
+ {"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+ gettext_noop("max active global temporary table."),
+ NULL
+ },
+ &max_active_gtt,
+ DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+ NULL, NULL, NULL
+ },
+ {
{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
gettext_noop("Forces a switch to the next WAL file if a "
"new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a12c8d0..58b22ea 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15595,6 +15595,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
{
char *ftoptions = NULL;
char *srvname = NULL;
+ char *table_type = NULL;
switch (tbinfo->relkind)
{
@@ -15646,9 +15647,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
binary_upgrade_set_pg_class_oids(fout, q,
tbinfo->dobj.catId.oid, false);
+ if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+ table_type = "UNLOGGED ";
+ else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ table_type = "GLOBAL TEMPORARY ";
+ else
+ table_type = "";
+
appendPQExpBuffer(q, "CREATE %s%s %s",
- tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
- "UNLOGGED " : "",
+ table_type,
reltypename,
qualrelname);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 5f9a102..3e40297 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
start_postmaster(&old_cluster, true);
/* Extract a list of databases and tables from the old cluster */
- get_db_and_rel_infos(&old_cluster);
+ get_db_and_rel_infos(&old_cluster, true);
init_tablespaces();
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
void
check_new_cluster(void)
{
- get_db_and_rel_infos(&new_cluster);
+ get_db_and_rel_infos(&new_cluster, false);
check_new_cluster_is_empty();
check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
bool is_new_db);
static void free_db_and_rel_infos(DbInfoArr *db_arr);
static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
static void free_rel_infos(RelInfoArr *rel_arr);
static void print_db_infos(DbInfoArr *dbinfo);
static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
*
* higher level routine to generate dbinfos for the database running
* on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
*/
void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
{
int dbnum;
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
get_db_infos(cluster);
for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
- get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+ get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
if (cluster == &old_cluster)
pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
* This allows later processing to match up old and new databases efficiently.
*/
static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
{
PGconn *conn = connectToServer(cluster,
dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
* output, so we have to copy that system table. It's easiest to do that
* by treating it as a user table.
*/
- snprintf(query + strlen(query), sizeof(query) - strlen(query),
- "WITH regular_heap (reloid, indtable, toastheap) AS ( "
- " SELECT c.oid, 0::oid, 0::oid "
- " FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
- " ON c.relnamespace = n.oid "
- " WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
- CppAsString2(RELKIND_MATVIEW) ") AND "
- /* exclude possible orphaned temp tables */
- " ((n.nspname !~ '^pg_temp_' AND "
- " n.nspname !~ '^pg_toast_temp_' AND "
- " n.nspname NOT IN ('pg_catalog', 'information_schema', "
- " 'binary_upgrade', 'pg_toast') AND "
- " c.oid >= %u::pg_catalog.oid) OR "
- " (n.nspname = 'pg_catalog' AND "
- " relname IN ('pg_largeobject') ))), ",
- FirstNormalObjectId);
+ if (skip_gtt)
+ {
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
+ "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+ " SELECT c.oid, 0::oid, 0::oid "
+ " FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+ " ON c.relnamespace = n.oid "
+ " WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+ CppAsString2(RELKIND_MATVIEW) ") AND "
+ /* exclude global temp tables */
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+ /* exclude possible orphaned temp tables */
+ " ((n.nspname !~ '^pg_temp_' AND "
+ " n.nspname !~ '^pg_toast_temp_' AND "
+ " n.nspname NOT IN ('pg_catalog', 'information_schema', "
+ " 'binary_upgrade', 'pg_toast') AND "
+ " c.oid >= %u::pg_catalog.oid) OR "
+ " (n.nspname = 'pg_catalog' AND "
+ " relname IN ('pg_largeobject') ))), ",
+ FirstNormalObjectId);
+ }
+ else
+ {
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
+ "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+ " SELECT c.oid, 0::oid, 0::oid "
+ " FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+ " ON c.relnamespace = n.oid "
+ " WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+ CppAsString2(RELKIND_MATVIEW) ") AND "
+ /* exclude possible orphaned temp tables */
+ " ((n.nspname !~ '^pg_temp_' AND "
+ " n.nspname !~ '^pg_toast_temp_' AND "
+ " n.nspname NOT IN ('pg_catalog', 'information_schema', "
+ " 'binary_upgrade', 'pg_toast') AND "
+ " c.oid >= %u::pg_catalog.oid) OR "
+ " (n.nspname = 'pg_catalog' AND "
+ " relname IN ('pg_largeobject') ))), ",
+ FirstNormalObjectId);
+ }
/*
* Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
set_frozenxids(true);
/* update new_cluster info now that we have objects in the databases */
- get_db_and_rel_infos(&new_cluster);
+ get_db_and_rel_infos(&new_cluster, true);
}
/*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
"UPDATE pg_catalog.pg_class "
"SET relfrozenxid = '%u' "
/* only heap, materialized view, and TOAST are vacuumed */
- "WHERE relkind IN ("
+ "WHERE "
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+ "relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
"UPDATE pg_catalog.pg_class "
"SET relminmxid = '%u' "
/* only heap, materialized view, and TOAST are vacuumed */
- "WHERE relkind IN ("
+ "WHERE "
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+ "relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void check_loadable_libraries(void);
FileNameMap *gen_db_file_maps(DbInfo *old_db,
DbInfo *new_db, int *nmaps, const char *old_pgdata,
const char *new_pgdata);
-void get_db_and_rel_infos(ClusterInfo *cluster);
+void get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
void print_maps(FileNameMap *maps, int n,
const char *db_name);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
if (pset.sversion >= 90100)
{
appendPQExpBuffer(&buf,
- ",\n CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+ ",\n CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+ gettext_noop("session"),
gettext_noop("permanent"),
gettext_noop("temporary"),
gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b6b08d0..16377e6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1016,6 +1016,8 @@ static const pgsql_thing_t words_after_create[] = {
{"FOREIGN TABLE", NULL, NULL, NULL},
{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
{"GROUP", Query_for_list_of_roles},
+ {"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+ * ... */
{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
{"LANGUAGE", Query_for_list_of_languages},
{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2378,6 +2380,9 @@ psql_completion(const char *text, int start, int end)
/* CREATE FOREIGN DATA WRAPPER */
else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+ /* CREATE GLOBAL TEMP/TEMPORARY*/
+ else if (Matches("CREATE", "GLOBAL"))
+ COMPLETE_WITH("TEMP", "TEMPORARY");
/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2586,6 +2591,8 @@ psql_completion(const char *text, int start, int end)
/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+ else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+ COMPLETE_WITH("TABLE");
/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
else if (TailMatches("CREATE", "UNLOGGED"))
COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
#define RELPERSISTENCE_PERMANENT 'p' /* regular table */
#define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */
#define RELPERSISTENCE_TEMP 't' /* temporary table */
+#define RELPERSISTENCE_GLOBAL_TEMP 'g' /* global temporary table */
/* default selection for replica identity (primary key or nothing) */
#define REPLICA_IDENTITY_DEFAULT 'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f..6f8591c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5507,6 +5507,40 @@
proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
prosrc => 'pg_stat_get_xact_function_self_time' },
+# For global temporary table
+{ oid => '4191',
+ descr => 'List local statistics for global temporary table',
+ proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+ proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+ proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+ proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+ prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+ descr => 'List local relstats for global temporary table',
+ proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+ proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+ proargmodes => '{i,o,o,o,o,o}',
+ proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+ prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+ descr => 'List attached pid for one global temporary table',
+ proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+ proallargtypes => '{oid,oid,int4}',
+ proargmodes => '{i,o,o}',
+ proargnames => '{relid,relid,pid}',
+ prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+ descr => 'List those backends that have used global temporary table',
+ proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+ proallargtypes => '{int4,xid}',
+ proargmodes => '{o,o}',
+ proargnames => '{pid,relfrozenxid}',
+ prosrc => 'pg_list_gtt_relfrozenxids' },
+
{ oid => '3788',
descr => 'statistics: timestamp of the current statistics snapshot',
proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
#include "storage/smgr.h"
#include "utils/relcache.h"
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
extern void RelationDropStorage(Relation rel);
extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..63f7241
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,43 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ * prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, bool isCommit);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+
+#endif /* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
extern const char *seq_identify(uint8 info);
extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
#endif /* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid attnumTypeId(Relation rd, int attid);
extern Oid attnumCollationId(Relation rd, int attid);
extern bool isQueryUsingTempRelation(Query *query);
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
#endif /* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
#define PageClearPrunable(page) \
(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+ ((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
/* ----------------------------------------------------------------
* extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_TBM,
LWTRANCHE_PARALLEL_APPEND,
LWTRANCHE_SXACT,
+ LWTRANCHE_GTT_CTL,
LWTRANCHE_FIRST_USER_DEFINED
} BuiltinTrancheIds;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
Oid tempNamespaceId; /* OID of temp schema this backend is
* using */
+ TransactionId session_gtt_frozenxid; /* session level global temp table relfrozenxid */
+
bool isBackgroundWorker; /* true if background worker. */
/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
TransactionId *catalog_xmin);
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
#endif /* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int tcp_user_timeout;
extern bool trace_sort;
#endif
+/* global temporary table */
+extern int max_active_gtt;
+/* end */
+
/*
* Functions exported by guc.c
*/
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..6b6d2da 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
struct SMgrRelationData *rd_smgr; /* cached file handle, or NULL */
int rd_refcnt; /* reference count */
BackendId rd_backend; /* owning backend id, if temporary relation */
- bool rd_islocaltemp; /* rel is a temp rel of this session */
+ bool rd_islocaltemp; /* rel is a temp rel of this session */
bool rd_isnailed; /* rel is nailed in cache */
bool rd_isvalid; /* relcache entry is valid */
bool rd_indexvalid; /* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
int parallel_workers; /* max number of parallel workers */
bool vacuum_index_cleanup; /* enables index vacuuming and cleanup */
bool vacuum_truncate; /* enables vacuum to truncate a relation */
+ bool on_commit_delete_rows; /* global temp table */
} StdRdOptions;
#define HEAP_MIN_FILLFACTOR 10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
* True if relation's pages are stored in local buffers.
*/
#define RelationUsesLocalBuffers(relation) \
- ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
/*
* RELATION_IS_LOCAL
- * If a rel is either temp or newly created in the current transaction,
+ * If a rel is either local temp or global temp relation
+ * or newly created in the current transaction,
* it can be assumed to be accessible only to the current backend.
* This is typically used to decide that we can skip acquiring locks.
*
@@ -547,6 +550,7 @@ typedef struct ViewOptions
*/
#define RELATION_IS_LOCAL(relation) \
((relation)->rd_islocaltemp || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
(relation)->rd_createSubid != InvalidSubTransactionId)
/*
@@ -559,6 +563,14 @@ typedef struct ViewOptions
((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
!(relation)->rd_islocaltemp)
+/*
+ * RELATION_IS_TEMP
+ * Test a rel is either local temp relation of this session
+ * or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+ ((relation)->rd_islocaltemp || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
/*
* RelationIsScannable
@@ -602,6 +614,17 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation) ((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation) \
+ ((relation)->rd_options && \
+ ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+ ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..73d3d7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,8 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE: drop cascades to 5 other objects
+DETAIL: drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..067393b
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,322 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+ product_no integer PRIMARY KEY,
+ name text,
+ price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR: cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR: not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR: ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR: not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR: The global temp table does not allow modification on commit action.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR: The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ok
+CREATE global temp TABLE measurement (
+ logdate date not null,
+ peaktemp int,
+ unitsales int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count
+-------
+ 2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count
+-------
+ 0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR: The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname;
+ relname | relkind | relpersistence | reloptions
+---------------------------------+---------+----------------+-------------------------------
+ p_table01 | p | g | {on_commit_delete_rows=true}
+ p_table01_2017 | r | g | {on_commit_delete_rows=true}
+ p_table01_2018 | r | g | {on_commit_delete_rows=true}
+ p_table01_id_seq | S | g |
+ p_table02 | p | g | {on_commit_delete_rows=false}
+ p_table02_2017 | r | g | {on_commit_delete_rows=false}
+ p_table02_2018 | r | g | {on_commit_delete_rows=false}
+ p_table02_id_seq | S | g |
+ tbl_inherits_parent | r | p |
+ tbl_inherits_parent_global_temp | r | g | {on_commit_delete_rows=true}
+ tbl_inherits_partition | r | g | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR: global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR: can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+ERROR: constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR: insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL: Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count
+-------
+ 1
+(1 row)
+
+select count(*) from orders;
+ count
+-------
+ 0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+ id SERIAL PRIMARY KEY,
+ data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a
+----+---
+ 2 | 1
+ 3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a
+----+---
+ 4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR: materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on idx_gt1_1
+ Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: (a = 200000)
+ -> Bitmap Index Scan on idx_gt1_1
+ Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: ((a * 10) = 300)
+ -> Bitmap Index Scan on idx_gt1_3
+ Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: ((a * 10) = 3)
+ -> Bitmap Index Scan on idx_gt1_3
+ Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+ QUERY PLAN
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+ Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+ QUERY PLAN
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+ Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+ QUERY PLAN
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+ Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+ QUERY PLAN
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+ Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE: drop cascades to 19 other objects
+DETAIL: drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval
+---------
+ 1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..b2ef23b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,168 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+ QUERY PLAN
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+ Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+ relname | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon | 450560 | 8192 | 499712 | 114688 | 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+ relname
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+ relname | pg_relation_size | pg_table_size | pg_total_relation_size
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 | 65536 | 65536 | 65536
+ idx_gtt_t_kenyon_2 | 49152 | 49152 | 49152
+(2 rows)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2
+----+----
+ 1 | 1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..0e21b30
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible
+------------+-----------+----------+-----------+---------------
+ gtt_stats | gtt | 0 | 0 | 0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible
+------------+-----------+----------+-----------+---------------
+ gtt_stats | gtt | 55 | 10000 | 0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats | gtt | a | f | 0 | 4 | -1 | | | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} | 1 | | |
+ gtt_stats | gtt | b | f | 0 | 5 | 1 | {test} | {1} | | 1 | | |
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE: drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index c730461..8a39594 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
FROM pg_authid
WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.relid,
+ s.pid
+ FROM (pg_class c
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.relpages,
+ s.reltuples,
+ s.relallvisible,
+ s.relfrozenxid,
+ s.relminmxid
+ FROM (pg_class c
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN (s.stakind1 = 1) THEN s.stavalues1
+ WHEN (s.stakind2 = 1) THEN s.stavalues2
+ WHEN (s.stakind3 = 1) THEN s.stavalues3
+ WHEN (s.stakind4 = 1) THEN s.stavalues4
+ WHEN (s.stakind5 = 1) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS most_common_vals,
+ CASE
+ WHEN (s.stakind1 = 1) THEN s.stanumbers1
+ WHEN (s.stakind2 = 1) THEN s.stanumbers2
+ WHEN (s.stakind3 = 1) THEN s.stanumbers3
+ WHEN (s.stakind4 = 1) THEN s.stanumbers4
+ WHEN (s.stakind5 = 1) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS most_common_freqs,
+ CASE
+ WHEN (s.stakind1 = 2) THEN s.stavalues1
+ WHEN (s.stakind2 = 2) THEN s.stavalues2
+ WHEN (s.stakind3 = 2) THEN s.stavalues3
+ WHEN (s.stakind4 = 2) THEN s.stavalues4
+ WHEN (s.stakind5 = 2) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS histogram_bounds,
+ CASE
+ WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+ WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+ WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+ WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+ WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+ ELSE NULL::real
+ END AS correlation,
+ CASE
+ WHEN (s.stakind1 = 4) THEN s.stavalues1
+ WHEN (s.stakind2 = 4) THEN s.stavalues2
+ WHEN (s.stakind3 = 4) THEN s.stavalues3
+ WHEN (s.stakind4 = 4) THEN s.stavalues4
+ WHEN (s.stakind5 = 4) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS most_common_elems,
+ CASE
+ WHEN (s.stakind1 = 4) THEN s.stanumbers1
+ WHEN (s.stakind2 = 4) THEN s.stanumbers2
+ WHEN (s.stakind3 = 4) THEN s.stanumbers3
+ WHEN (s.stakind4 = 4) THEN s.stanumbers4
+ WHEN (s.stakind5 = 4) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN (s.stakind1 = 5) THEN s.stanumbers1
+ WHEN (s.stakind2 = 5) THEN s.stanumbers2
+ WHEN (s.stakind3 = 5) THEN s.stanumbers3
+ WHEN (s.stakind4 = 5) THEN s.stanumbers4
+ WHEN (s.stakind5 = 5) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS elem_count_histogram
+ FROM ((pg_class c
+ JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
pg_hba_file_rules| SELECT a.line_number,
a.type,
a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
# run stats by itself because its delay may be insufficient under heavy load
test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..c32c0bd
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,217 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+ product_no integer PRIMARY KEY,
+ name text,
+ price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+ logdate date not null,
+ peaktemp int,
+ unitsales int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+ id SERIAL PRIMARY KEY,
+ data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..bf0b922
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,79 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
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/>
The Enterprise PostgreSQL Company
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
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.patchWenjing
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.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
postgres=#
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
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
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=# \qCase 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"
VACUUMAlthough 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.patchWenjing
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/>
The Enterprise PostgreSQL Company--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ {
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ }
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+ int natts = 0;
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ elog(ERROR, "backend %d relid %u already exists in global temporary table local hash",
+ MyBackendId, relid);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry->spcnode = rnode.spcNode;
+ entry->relpages = 0;
+ entry->reltuples = 0;
+ entry->relallvisible = 0;
+ entry->relfrozenxid = InvalidTransactionId;
+ entry->relminmxid = InvalidMultiXactId;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+ }
+
+ entry->relfrozenxid = RecentXmin;
+ entry->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(rnode);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, NULL);
+
+ if (entry)
+ {
+ int i;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(entry->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(entry->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ RelFileNode rnode;
+ rnode.spcNode = entry->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = entry->relid;
+ gtt_storage_checkout(rnode, false, isCommit);
+ }
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->att_stat_tups[i])
+ {
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ }
+ }
+
+ if (entry->attnum)
+ pfree(entry->attnum);
+
+ if (entry->att_stat_tups)
+ pfree(entry->att_stat_tups);
+ }
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, &found);
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ int nrels = 0,
+ maxrels = 0;
+ SMgrRelation *srels = NULL;
+ RelFileNode *rnodes = NULL;
+ char *relkinds = NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+
+ rnode.spcNode = entry->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = entry->relid;
+
+ srel = smgropen(rnode, MyBackendId);
+
+ /* allocate the initial array, or extend it, if needed */
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxrels);
+ rnodes = palloc(sizeof(RelFileNode) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+ rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ srels[nrels] = srel;
+ rnodes[nrels] = rnode;
+ relkinds[nrels] = entry->relkind;
+ nrels++;
+ }
+
+ if (nrels > 0)
+ {
+ int i;
+
+ smgrdounlinkall(srels, nrels, false);
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(rnodes[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ /* todo */
+ if (entry->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ Assert(entry->relid == reloid);
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == 0)
+ {
+ entry->attnum[i] = attnum;
+ break;
+ }
+ else if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < entry->natts);
+ Assert(entry->att_stat_tups[i] == NULL);
+ entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return NULL;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ return entry->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+ MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ HeapTuple tuple;
+ Relation rel = NULL;
+ int attnum = PG_GETARG_INT32(1);
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Tuplestorestate *tupstore;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ tuple = get_gtt_att_statistic(reloid, attnum, false);
+ if (tuple)
+ {
+ Datum values[31];
+ bool isnull[31];
+ HeapTuple res = NULL;
+
+ memset(&values, 0, sizeof(values));
+ memset(&isnull, 0, sizeof(isnull));
+ heap_deform_tuple(tuple, sd, values, isnull);
+ res = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, res);
+ }
+
+ table_close(rel, NoLock);
+ table_close(pg_tatistic, AccessShareLock);
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ if (get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid))
+ {
+ Datum values[5];
+ bool isnull[5];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(relpages);
+ values[1] = Float4GetDatum((float4)reltuples);
+ values[2] = Int32GetDatum(relallvisible);
+ values[3] = UInt32GetDatum(relfrozenxid);
+ values[4] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ map = copy_active_gtt_bitmap(rel->rd_node);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ int i = 0;
+ int j = 0;
+ uint32 oldest = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ if (max_active_gtt <= 0)
+ return (Datum) 0;
+
+ if (RecoveryInProgress())
+ return (Datum) 0;
+
+ pids = palloc0(sizeof(int) * num_xid);
+ xids = palloc0(sizeof(int) * num_xid);
+ oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+ if (i > 0)
+ {
+ if (i > 0)
+ {
+ pids[i] = 0;
+ xids[i] = oldest;
+ i++;
+ }
+
+ for(j = 0; j < i; j++)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(pids[j]);
+ values[1] = UInt32GetDatum(xids[j]);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ }
+ tuplestore_donestoring(tupstore);
+ pfree(pids);
+ pfree(xids);
+
+ return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(indexOid));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid relOid = index->rd_index->indrelid;
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ if (gtt_storage_attached(relOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ int i;
+ Oid toastrelid;
+
+ if (operation != CMD_INSERT)
+ return;
+
+ if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+ return;
+
+ if (!RELATION_IS_GLOBAL_TEMP(relation))
+ return;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_attached(relation->rd_node.relNode))
+ return;
+
+ RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+ for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+ {
+ Relation index = resultRelInfo->ri_IndexRelationDescs[i];
+ IndexInfo *info = resultRelInfo->ri_IndexRelationInfo[i];
+
+ Assert(index->rd_index->indisvalid);
+ Assert(index->rd_index->indislive);
+ Assert(index->rd_index->indisready);
+
+ index_build(relation, index, info, true, false);
+ }
+
+ toastrelid = relation->rd_rel->reltoastrelid;
+ if (OidIsValid(toastrelid))
+ {
+ Relation toastrel;
+ ListCell *indlist;
+
+ toastrel = table_open(toastrelid, RowExclusiveLock);
+ RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+ foreach(indlist, RelationGetIndexList(toastrel))
+ {
+ Oid indexId = lfirst_oid(indlist);
+ Relation currentIndex;
+ IndexInfo *indexInfo;
+
+ currentIndex = index_open(indexId, RowExclusiveLock);
+
+ indexInfo = BuildDummyIndexInfo(currentIndex);
+ index_build(toastrel, currentIndex, indexInfo, true, false);
+ index_close(currentIndex, NoLock);
+ }
+
+ table_close(toastrel, NoLock);
+ }
+
+ return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46..491db16 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
WHERE NOT pg_is_other_temp_schema(N.oid)
AND relkind = 'S';
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stavalues1
+ WHEN s.stakind2 = 1 THEN s.stavalues2
+ WHEN s.stakind3 = 1 THEN s.stavalues3
+ WHEN s.stakind4 = 1 THEN s.stavalues4
+ WHEN s.stakind5 = 1 THEN s.stavalues5
+ END AS most_common_vals,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stanumbers1
+ WHEN s.stakind2 = 1 THEN s.stanumbers2
+ WHEN s.stakind3 = 1 THEN s.stanumbers3
+ WHEN s.stakind4 = 1 THEN s.stanumbers4
+ WHEN s.stakind5 = 1 THEN s.stanumbers5
+ END AS most_common_freqs,
+ CASE
+ WHEN s.stakind1 = 2 THEN s.stavalues1
+ WHEN s.stakind2 = 2 THEN s.stavalues2
+ WHEN s.stakind3 = 2 THEN s.stavalues3
+ WHEN s.stakind4 = 2 THEN s.stavalues4
+ WHEN s.stakind5 = 2 THEN s.stavalues5
+ END AS histogram_bounds,
+ CASE
+ WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+ WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+ WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+ WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+ WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+ END AS correlation,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stavalues1
+ WHEN s.stakind2 = 4 THEN s.stavalues2
+ WHEN s.stakind3 = 4 THEN s.stavalues3
+ WHEN s.stakind4 = 4 THEN s.stavalues4
+ WHEN s.stakind5 = 4 THEN s.stavalues5
+ END AS most_common_elems,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stanumbers1
+ WHEN s.stakind2 = 4 THEN s.stanumbers2
+ WHEN s.stakind3 = 4 THEN s.stanumbers3
+ WHEN s.stakind4 = 4 THEN s.stanumbers4
+ WHEN s.stakind5 = 4 THEN s.stanumbers5
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN s.stakind1 = 5 THEN s.stanumbers1
+ WHEN s.stakind2 = 5 THEN s.stanumbers2
+ WHEN s.stakind3 = 5 THEN s.stanumbers3
+ WHEN s.stakind4 = 5 THEN s.stanumbers4
+ WHEN s.stakind5 = 5 THEN s.stanumbers5
+ END AS elem_count_histogram
+ FROM
+ pg_class c
+ JOIN pg_attribute a ON c.oid = a.attrelid
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
CREATE VIEW pg_stats WITH (security_barrier) AS
SELECT
nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..7efc5a9 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
#include "commands/dbcommands.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int acquire_inherited_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
static void update_attstats(Oid relid, bool inh,
- int natts, VacAttrStats **vacattrstats);
+ int natts, VacAttrStats **vacattrstats, char relpersistence);
static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
return;
}
+ if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+ !gtt_storage_attached(onerel->rd_node.relNode))
+ {
+ relation_close(onerel, ShareUpdateExclusiveLock);
+ return;
+ }
+
/*
* We can ANALYZE any table except pg_statistic. See update_attstats
*/
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* pg_statistic for columns we didn't process, we leave them alone.)
*/
update_attstats(RelationGetRelid(onerel), inh,
- attr_cnt, vacattrstats);
+ attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
for (ind = 0; ind < nindexes; ind++)
{
AnlIndexData *thisdata = &indexdata[ind];
update_attstats(RelationGetRelid(Irel[ind]), false,
- thisdata->attr_cnt, thisdata->vacattrstats);
+ thisdata->attr_cnt, thisdata->vacattrstats,
+ RelationGetRelPersistence(Irel[ind]));
}
/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
* by taking a self-exclusive lock on the relation in analyze_rel().
*/
static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
{
Relation sd;
int attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
}
}
- /* Is there already a pg_statistic tuple for this attribute? */
- oldtup = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(relid),
- Int16GetDatum(stats->attr->attnum),
- BoolGetDatum(inh));
-
- if (HeapTupleIsValid(oldtup))
+ /* Update column statistic to localhash, not catalog */
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
{
- /* Yes, replace it */
- stup = heap_modify_tuple(oldtup,
- RelationGetDescr(sd),
- values,
- nulls,
- replaces);
- ReleaseSysCache(oldtup);
- CatalogTupleUpdate(sd, &stup->t_self, stup);
+ up_gtt_att_statistic(relid,
+ stats->attr->attnum,
+ inh,
+ natts,
+ RelationGetDescr(sd),
+ values,
+ nulls);
}
else
{
- /* No, insert new tuple */
- stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
- CatalogTupleInsert(sd, stup);
- }
+ /* Is there already a pg_statistic tuple for this attribute? */
+ oldtup = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(relid),
+ Int16GetDatum(stats->attr->attnum),
+ BoolGetDatum(inh));
- heap_freetuple(stup);
+ if (HeapTupleIsValid(oldtup))
+ {
+ /* Yes, replace it */
+ stup = heap_modify_tuple(oldtup,
+ RelationGetDescr(sd),
+ values,
+ nulls,
+ replaces);
+ ReleaseSysCache(oldtup);
+ CatalogTupleUpdate(sd, &stup->t_self, stup);
+ }
+ else
+ {
+ /* No, insert new tuple */
+ stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+ CatalogTupleInsert(sd, stup);
+ }
+
+ heap_freetuple(stup);
+ }
}
table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
errmsg("cannot vacuum temporary tables of other sessions")));
}
+ /* not support cluster global temp table yet */
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("not support cluster global temporary tables yet")));
+
/*
* Also check for active uses of the relation in the current transaction,
* including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e79ede4..078694d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
#include "catalog/dependency.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
#include "commands/copy.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
@@ -1062,7 +1063,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
Assert(rel);
/* check read-only transaction and parallel mode */
- if (XactReadOnly && !rel->rd_islocaltemp)
+ if (XactReadOnly && !RELATION_IS_TEMP(rel))
PreventCommandIfReadOnly("COPY FROM");
cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2840,6 +2841,8 @@ CopyFrom(CopyState cstate)
ExecOpenIndices(resultRelInfo, false);
+ init_gtt_storage(CMD_INSERT, resultRelInfo);
+
estate->es_result_relations = resultRelInfo;
estate->es_num_result_relations = 1;
estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 3f3a89f..928c01e 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2586,6 +2586,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
!isTempNamespace(classtuple->relnamespace))
continue;
+ /* not support reindex on global temp table, so skip it */
+ if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ ereport(WARNING,
+ (errmsg("global temp table \"%s.%s\" skip reindexed",
+ get_namespace_name(get_rel_namespace(relid)),
+ get_rel_name(relid))));
+ continue;
+ }
+
/* Check user/system classification, and optionally skip */
if (objectKind == REINDEX_OBJECT_SYSTEM &&
!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..deecde8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* transaction.
*/
relpersistence = get_rel_persistence(relid);
- if (relpersistence == RELPERSISTENCE_TEMP)
+ if (relpersistence == RELPERSISTENCE_TEMP ||
+ relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..fada1ac 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
#include "catalog/objectaccess.h"
#include "catalog/pg_sequence.h"
#include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
#include "commands/defrem.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
*/
static SeqTableData *last_used_seq = NULL;
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
static Relation lock_and_open_sequence(SeqTable seq);
static void create_seq_hashtable(void);
static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
List **owned_by);
static void do_setval(Oid relid, int64 next, bool iscalled);
static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
/*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
/* now initialize the sequence's data */
tuple = heap_form_tuple(tupDesc, value, null);
- fill_seq_with_data(rel, tuple);
+ fill_seq_with_data(rel, tuple, InvalidBuffer);
/* process OWNED BY if given */
if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
/*
* Insert the modified tuple into the new storage file.
*/
- fill_seq_with_data(seq_rel, tuple);
+ fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
/* Clear local cache so that we don't think we have cached numbers */
/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
* Initialize a sequence's relation with the specified tuple as content
*/
static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
{
- Buffer buf;
Page page;
sequence_magic *sm;
OffsetNumber offnum;
+ bool lockBuffer = false;
/* Initialize first page of relation with special magic number */
- buf = ReadBuffer(rel, P_NEW);
- Assert(BufferGetBlockNumber(buf) == 0);
+ if (buf == InvalidBuffer)
+ {
+ buf = ReadBuffer(rel, P_NEW);
+ Assert(BufferGetBlockNumber(buf) == 0);
+ lockBuffer = true;
+ }
page = BufferGetPage(buf);
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
/* Now insert sequence tuple */
- LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+ if (lockBuffer)
+ LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
/*
* Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
END_CRIT_SECTION();
- UnlockReleaseBuffer(buf);
+ if (lockBuffer)
+ UnlockReleaseBuffer(buf);
}
/*
@@ -502,7 +511,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
/*
* Insert the modified tuple into the new storage file.
*/
- fill_seq_with_data(seqrel, newdatatuple);
+ fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
}
/* process OWNED BY if given */
@@ -611,7 +620,7 @@ nextval_internal(Oid relid, bool check_permissions)
RelationGetRelationName(seqrel))));
/* read-only transactions may only modify temp sequences */
- if (!seqrel->rd_islocaltemp)
+ if (!RELATION_IS_TEMP(seqrel))
PreventCommandIfReadOnly("nextval()");
/*
@@ -936,7 +945,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
ReleaseSysCache(pgstuple);
/* read-only transactions may only modify temp sequences */
- if (!seqrel->rd_islocaltemp)
+ if (!RELATION_IS_TEMP(seqrel))
PreventCommandIfReadOnly("setval()");
/*
@@ -1154,6 +1163,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
/* Return results */
*p_elm = elm;
*p_rel = seqrel;
+
+ if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+ !gtt_storage_attached(seqrel->rd_node.relNode))
+ {
+ RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+ gtt_init_seq(seqrel);
+ }
}
@@ -1954,3 +1970,46 @@ seq_mask(char *page, BlockNumber blkno)
mask_unused_space(page);
}
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+ HeapTuple seqtuple;
+ Form_pg_sequence seqform;
+ int64 start;
+
+ seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+ if (!HeapTupleIsValid(seqtuple))
+ elog(ERROR, "cache lookup failed for sequence %u",
+ seqid);
+
+ seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+ start = seqform->seqstart;
+ ReleaseSysCache(seqtuple);
+
+ return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+ /* Initialize sequence for global temporary tables */
+ Datum value[SEQ_COL_LASTCOL] = {0};
+ bool null[SEQ_COL_LASTCOL] = {false};
+ HeapTuple tuple;
+ int64 startv = get_seqence_start_value(RelationGetRelid(rel));
+
+ /*
+ * last_value from pg_sequence.seqstart
+ * log_cnt = 0
+ * is_called = false
+ */
+ value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+ tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+ fill_seq_with_data(rel, tuple, InvalidBuffer);
+ heap_freetuple(tuple);
+
+ return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 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;
+
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ {
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ }
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+ int natts = 0;
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ elog(ERROR, "backend %d relid %u already exists in global temporary table local hash",
+ MyBackendId, relid);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry->spcnode = rnode.spcNode;
+ entry->relpages = 0;
+ entry->reltuples = 0;
+ entry->relallvisible = 0;
+ entry->relfrozenxid = InvalidTransactionId;
+ entry->relminmxid = InvalidMultiXactId;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+ }
+
+ entry->relfrozenxid = RecentXmin;
+ entry->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(rnode);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, NULL);
+
+ if (entry)
+ {
+ int i;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(entry->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(entry->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ RelFileNode rnode;
+ rnode.spcNode = entry->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = entry->relid;
+ gtt_storage_checkout(rnode, false, isCommit);
+ }
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->att_stat_tups[i])
+ {
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ }
+ }
+
+ if (entry->attnum)
+ pfree(entry->attnum);
+
+ if (entry->att_stat_tups)
+ pfree(entry->att_stat_tups);
+ }
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, &found);
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ int nrels = 0,
+ maxrels = 0;
+ SMgrRelation *srels = NULL;
+ RelFileNode *rnodes = NULL;
+ char *relkinds = NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+
+ rnode.spcNode = entry->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = entry->relid;
+
+ srel = smgropen(rnode, MyBackendId);
+
+ /* allocate the initial array, or extend it, if needed */
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxrels);
+ rnodes = palloc(sizeof(RelFileNode) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+ rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ srels[nrels] = srel;
+ rnodes[nrels] = rnode;
+ relkinds[nrels] = entry->relkind;
+ nrels++;
+ }
+
+ if (nrels > 0)
+ {
+ int i;
+
+ smgrdounlinkall(srels, nrels, false);
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(rnodes[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ /* todo */
+ if (entry->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ Assert(entry->relid == reloid);
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == 0)
+ {
+ entry->attnum[i] = attnum;
+ break;
+ }
+ else if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < entry->natts);
+ Assert(entry->att_stat_tups[i] == NULL);
+ entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return NULL;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ return entry->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+ MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ HeapTuple tuple;
+ Relation rel = NULL;
+ int attnum = PG_GETARG_INT32(1);
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Tuplestorestate *tupstore;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ tuple = get_gtt_att_statistic(reloid, attnum, false);
+ if (tuple)
+ {
+ Datum values[31];
+ bool isnull[31];
+ HeapTuple res = NULL;
+
+ memset(&values, 0, sizeof(values));
+ memset(&isnull, 0, sizeof(isnull));
+ heap_deform_tuple(tuple, sd, values, isnull);
+ res = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, res);
+ }
+
+ table_close(rel, NoLock);
+ table_close(pg_tatistic, AccessShareLock);
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ if (get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid))
+ {
+ Datum values[5];
+ bool isnull[5];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(relpages);
+ values[1] = Float4GetDatum((float4)reltuples);
+ values[2] = Int32GetDatum(relallvisible);
+ values[3] = UInt32GetDatum(relfrozenxid);
+ values[4] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ map = copy_active_gtt_bitmap(rel->rd_node);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ int i = 0;
+ int j = 0;
+ uint32 oldest = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ if (max_active_gtt <= 0)
+ return (Datum) 0;
+
+ if (RecoveryInProgress())
+ return (Datum) 0;
+
+ pids = palloc0(sizeof(int) * num_xid);
+ xids = palloc0(sizeof(int) * num_xid);
+ oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+ if (i > 0)
+ {
+ if (i > 0)
+ {
+ pids[i] = 0;
+ xids[i] = oldest;
+ i++;
+ }
+
+ for(j = 0; j < i; j++)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(pids[j]);
+ values[1] = UInt32GetDatum(xids[j]);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ }
+ tuplestore_donestoring(tupstore);
+ pfree(pids);
+ pfree(xids);
+
+ return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(indexOid));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid relOid = index->rd_index->indrelid;
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ if (gtt_storage_attached(relOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ int i;
+ Oid toastrelid;
+
+ if (operation != CMD_INSERT)
+ return;
+
+ if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+ return;
+
+ if (!RELATION_IS_GLOBAL_TEMP(relation))
+ return;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_attached(relation->rd_node.relNode))
+ return;
+
+ RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+ for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+ {
+ Relation index = resultRelInfo->ri_IndexRelationDescs[i];
+ IndexInfo *info = resultRelInfo->ri_IndexRelationInfo[i];
+
+ Assert(index->rd_index->indisvalid);
+ Assert(index->rd_index->indislive);
+ Assert(index->rd_index->indisready);
+
+ index_build(relation, index, info, true, false);
+ }
+
+ toastrelid = relation->rd_rel->reltoastrelid;
+ if (OidIsValid(toastrelid))
+ {
+ Relation toastrel;
+ ListCell *indlist;
+
+ toastrel = table_open(toastrelid, RowExclusiveLock);
+ RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+ foreach(indlist, RelationGetIndexList(toastrel))
+ {
+ Oid indexId = lfirst_oid(indlist);
+ Relation currentIndex;
+ IndexInfo *indexInfo;
+
+ currentIndex = index_open(indexId, RowExclusiveLock);
+
+ indexInfo = BuildDummyIndexInfo(currentIndex);
+ index_build(toastrel, currentIndex, indexInfo, true, false);
+ index_close(currentIndex, NoLock);
+ }
+
+ table_close(toastrel, NoLock);
+ }
+
+ return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46..491db16 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
WHERE NOT pg_is_other_temp_schema(N.oid)
AND relkind = 'S';
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stavalues1
+ WHEN s.stakind2 = 1 THEN s.stavalues2
+ WHEN s.stakind3 = 1 THEN s.stavalues3
+ WHEN s.stakind4 = 1 THEN s.stavalues4
+ WHEN s.stakind5 = 1 THEN s.stavalues5
+ END AS most_common_vals,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stanumbers1
+ WHEN s.stakind2 = 1 THEN s.stanumbers2
+ WHEN s.stakind3 = 1 THEN s.stanumbers3
+ WHEN s.stakind4 = 1 THEN s.stanumbers4
+ WHEN s.stakind5 = 1 THEN s.stanumbers5
+ END AS most_common_freqs,
+ CASE
+ WHEN s.stakind1 = 2 THEN s.stavalues1
+ WHEN s.stakind2 = 2 THEN s.stavalues2
+ WHEN s.stakind3 = 2 THEN s.stavalues3
+ WHEN s.stakind4 = 2 THEN s.stavalues4
+ WHEN s.stakind5 = 2 THEN s.stavalues5
+ END AS histogram_bounds,
+ CASE
+ WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+ WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+ WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+ WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+ WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+ END AS correlation,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stavalues1
+ WHEN s.stakind2 = 4 THEN s.stavalues2
+ WHEN s.stakind3 = 4 THEN s.stavalues3
+ WHEN s.stakind4 = 4 THEN s.stavalues4
+ WHEN s.stakind5 = 4 THEN s.stavalues5
+ END AS most_common_elems,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stanumbers1
+ WHEN s.stakind2 = 4 THEN s.stanumbers2
+ WHEN s.stakind3 = 4 THEN s.stanumbers3
+ WHEN s.stakind4 = 4 THEN s.stanumbers4
+ WHEN s.stakind5 = 4 THEN s.stanumbers5
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN s.stakind1 = 5 THEN s.stanumbers1
+ WHEN s.stakind2 = 5 THEN s.stanumbers2
+ WHEN s.stakind3 = 5 THEN s.stanumbers3
+ WHEN s.stakind4 = 5 THEN s.stanumbers4
+ WHEN s.stakind5 = 5 THEN s.stanumbers5
+ END AS elem_count_histogram
+ FROM
+ pg_class c
+ JOIN pg_attribute a ON c.oid = a.attrelid
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
CREATE VIEW pg_stats WITH (security_barrier) AS
SELECT
nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..7efc5a9 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
#include "commands/dbcommands.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int acquire_inherited_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
static void update_attstats(Oid relid, bool inh,
- int natts, VacAttrStats **vacattrstats);
+ int natts, VacAttrStats **vacattrstats, char relpersistence);
static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
return;
}
+ if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+ !gtt_storage_attached(onerel->rd_node.relNode))
+ {
+ relation_close(onerel, ShareUpdateExclusiveLock);
+ return;
+ }
+
/*
* We can ANALYZE any table except pg_statistic. See update_attstats
*/
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* pg_statistic for columns we didn't process, we leave them alone.)
*/
update_attstats(RelationGetRelid(onerel), inh,
- attr_cnt, vacattrstats);
+ attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
for (ind = 0; ind < nindexes; ind++)
{
AnlIndexData *thisdata = &indexdata[ind];
update_attstats(RelationGetRelid(Irel[ind]), false,
- thisdata->attr_cnt, thisdata->vacattrstats);
+ thisdata->attr_cnt, thisdata->vacattrstats,
+ RelationGetRelPersistence(Irel[ind]));
}
/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
* by taking a self-exclusive lock on the relation in analyze_rel().
*/
static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
{
Relation sd;
int attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
}
}
- /* Is there already a pg_statistic tuple for this attribute? */
- oldtup = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(relid),
- Int16GetDatum(stats->attr->attnum),
- BoolGetDatum(inh));
-
- if (HeapTupleIsValid(oldtup))
+ /* Update column statistic to localhash, not catalog */
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
{
- /* Yes, replace it */
- stup = heap_modify_tuple(oldtup,
- RelationGetDescr(sd),
- values,
- nulls,
- replaces);
- ReleaseSysCache(oldtup);
- CatalogTupleUpdate(sd, &stup->t_self, stup);
+ up_gtt_att_statistic(relid,
+ stats->attr->attnum,
+ inh,
+ natts,
+ RelationGetDescr(sd),
+ values,
+ nulls);
}
else
{
- /* No, insert new tuple */
- stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
- CatalogTupleInsert(sd, stup);
- }
+ /* Is there already a pg_statistic tuple for this attribute? */
+ oldtup = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(relid),
+ Int16GetDatum(stats->attr->attnum),
+ BoolGetDatum(inh));
- heap_freetuple(stup);
+ if (HeapTupleIsValid(oldtup))
+ {
+ /* Yes, replace it */
+ stup = heap_modify_tuple(oldtup,
+ RelationGetDescr(sd),
+ values,
+ nulls,
+ replaces);
+ ReleaseSysCache(oldtup);
+ CatalogTupleUpdate(sd, &stup->t_self, stup);
+ }
+ else
+ {
+ /* No, insert new tuple */
+ stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+ CatalogTupleInsert(sd, stup);
+ }
+
+ heap_freetuple(stup);
+ }
}
table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
errmsg("cannot vacuum temporary tables of other sessions")));
}
+ /* not support cluster global temp table yet */
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("not support cluster global temporary tables yet")));
+
/*
* Also check for active uses of the relation in the current transaction,
* including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e79ede4..078694d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
#include "catalog/dependency.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
#include "commands/copy.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
@@ -1062,7 +1063,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
Assert(rel);
/* check read-only transaction and parallel mode */
- if (XactReadOnly && !rel->rd_islocaltemp)
+ if (XactReadOnly && !RELATION_IS_TEMP(rel))
PreventCommandIfReadOnly("COPY FROM");
cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2840,6 +2841,8 @@ CopyFrom(CopyState cstate)
ExecOpenIndices(resultRelInfo, false);
+ init_gtt_storage(CMD_INSERT, resultRelInfo);
+
estate->es_result_relations = resultRelInfo;
estate->es_num_result_relations = 1;
estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 3f3a89f..928c01e 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2586,6 +2586,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
!isTempNamespace(classtuple->relnamespace))
continue;
+ /* not support reindex on global temp table, so skip it */
+ if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ ereport(WARNING,
+ (errmsg("global temp table \"%s.%s\" skip reindexed",
+ get_namespace_name(get_rel_namespace(relid)),
+ get_rel_name(relid))));
+ continue;
+ }
+
/* Check user/system classification, and optionally skip */
if (objectKind == REINDEX_OBJECT_SYSTEM &&
!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..deecde8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* transaction.
*/
relpersistence = get_rel_persistence(relid);
- if (relpersistence == RELPERSISTENCE_TEMP)
+ if (relpersistence == RELPERSISTENCE_TEMP ||
+ relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..fada1ac 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
#include "catalog/objectaccess.h"
#include "catalog/pg_sequence.h"
#include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
#include "commands/defrem.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
*/
static SeqTableData *last_used_seq = NULL;
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
static Relation lock_and_open_sequence(SeqTable seq);
static void create_seq_hashtable(void);
static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
List **owned_by);
static void do_setval(Oid relid, int64 next, bool iscalled);
static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
/*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
/* now initialize the sequence's data */
tuple = heap_form_tuple(tupDesc, value, null);
- fill_seq_with_data(rel, tuple);
+ fill_seq_with_data(rel, tuple, InvalidBuffer);
/* process OWNED BY if given */
if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
/*
* Insert the modified tuple into the new storage file.
*/
- fill_seq_with_data(seq_rel, tuple);
+ fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
/* Clear local cache so that we don't think we have cached numbers */
/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
* Initialize a sequence's relation with the specified tuple as content
*/
static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
{
- Buffer buf;
Page page;
sequence_magic *sm;
OffsetNumber offnum;
+ bool lockBuffer = false;
/* Initialize first page of relation with special magic number */
- buf = ReadBuffer(rel, P_NEW);
- Assert(BufferGetBlockNumber(buf) == 0);
+ if (buf == InvalidBuffer)
+ {
+ buf = ReadBuffer(rel, P_NEW);
+ Assert(BufferGetBlockNumber(buf) == 0);
+ lockBuffer = true;
+ }
page = BufferGetPage(buf);
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
/* Now insert sequence tuple */
- LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+ if (lockBuffer)
+ LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
/*
* Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
END_CRIT_SECTION();
- UnlockReleaseBuffer(buf);
+ if (lockBuffer)
+ UnlockReleaseBuffer(buf);
}
/*
@@ -502,7 +511,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
/*
* Insert the modified tuple into the new storage file.
*/
- fill_seq_with_data(seqrel, newdatatuple);
+ fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
}
/* process OWNED BY if given */
@@ -611,7 +620,7 @@ nextval_internal(Oid relid, bool check_permissions)
RelationGetRelationName(seqrel))));
/* read-only transactions may only modify temp sequences */
- if (!seqrel->rd_islocaltemp)
+ if (!RELATION_IS_TEMP(seqrel))
PreventCommandIfReadOnly("nextval()");
/*
@@ -936,7 +945,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
ReleaseSysCache(pgstuple);
/* read-only transactions may only modify temp sequences */
- if (!seqrel->rd_islocaltemp)
+ if (!RELATION_IS_TEMP(seqrel))
PreventCommandIfReadOnly("setval()");
/*
@@ -1154,6 +1163,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
/* Return results */
*p_elm = elm;
*p_rel = seqrel;
+
+ if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+ !gtt_storage_attached(seqrel->rd_node.relNode))
+ {
+ RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+ gtt_init_seq(seqrel);
+ }
}
@@ -1954,3 +1970,46 @@ seq_mask(char *page, BlockNumber blkno)
mask_unused_space(page);
}
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+ HeapTuple seqtuple;
+ Form_pg_sequence seqform;
+ int64 start;
+
+ seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+ if (!HeapTupleIsValid(seqtuple))
+ elog(ERROR, "cache lookup failed for sequence %u",
+ seqid);
+
+ seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+ start = seqform->seqstart;
+ ReleaseSysCache(seqtuple);
+
+ return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+ /* Initialize sequence for global temporary tables */
+ Datum value[SEQ_COL_LASTCOL] = {0};
+ bool null[SEQ_COL_LASTCOL] = {false};
+ HeapTuple tuple;
+ int64 startv = get_seqence_start_value(RelationGetRelid(rel));
+
+ /*
+ * last_value from pg_sequence.seqstart
+ * log_cnt = 0
+ * is_called = false
+ */
+ value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+ tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+ fill_seq_with_data(rel, tuple, InvalidBuffer);
+ heap_freetuple(tuple);
+
+ return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 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;
+
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
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
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 SEQUENCEpostgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
CREATE TABLEpostgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
CREATE TABLEpostgres=# alter table gtt1 add c2 int default nextval('seq');
ERROR: cannot reindex global temporary tablespostgres=# alter table gtt2 add c2 int default nextval('seq');
ERROR: cannot reindex global temporary tablesNote: 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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(RelFileNode);
+ info.entrysize = gtt_shared_ctl->entry_size;
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+ &rnode, HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(rnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ {
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ }
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(node), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ Oid relid = rnode.relNode;
+ bool found;
+ int natts = 0;
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ elog(ERROR, "backend %d relid %u already exists in global temporary table local hash",
+ MyBackendId, relid);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry->spcnode = rnode.spcNode;
+ entry->relpages = 0;
+ entry->reltuples = 0;
+ entry->relallvisible = 0;
+ entry->relfrozenxid = InvalidTransactionId;
+ entry->relminmxid = InvalidMultiXactId;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+ }
+
+ entry->relfrozenxid = RecentXmin;
+ entry->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(rnode);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, NULL);
+
+ if (entry)
+ {
+ int i;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(entry->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(entry->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ RelFileNode rnode;
+ rnode.spcNode = entry->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = entry->relid;
+ gtt_storage_checkout(rnode, false, isCommit);
+ }
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->att_stat_tups[i])
+ {
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ }
+ }
+
+ if (entry->attnum)
+ pfree(entry->attnum);
+
+ if (entry->att_stat_tups)
+ pfree(entry->att_stat_tups);
+ }
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, &found);
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ int nrels = 0,
+ maxrels = 0;
+ SMgrRelation *srels = NULL;
+ RelFileNode *rnodes = NULL;
+ char *relkinds = NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+
+ rnode.spcNode = entry->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = entry->relid;
+
+ srel = smgropen(rnode, MyBackendId);
+
+ /* allocate the initial array, or extend it, if needed */
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxrels);
+ rnodes = palloc(sizeof(RelFileNode) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+ rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ srels[nrels] = srel;
+ rnodes[nrels] = rnode;
+ relkinds[nrels] = entry->relkind;
+ nrels++;
+ }
+
+ if (nrels > 0)
+ {
+ int i;
+
+ smgrdounlinkall(srels, nrels, false);
+
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ smgrclose(srels[i]);
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(rnodes[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(srels);
+ pfree(rnodes);
+ pfree(relkinds);
+ }
+
+ MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ Oid relid = RelationGetRelid(relation);
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ Assert(entry->spcnode);
+
+ if (num_pages >= 0 &&
+ entry->relpages != (int32)num_pages)
+ entry->relpages = (int32)num_pages;
+
+ if (num_tuples >= 0 &&
+ num_tuples != (float4)entry->reltuples)
+ entry->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (entry->relallvisible >= 0 &&
+ entry->relallvisible != (int32)num_all_visible_pages)
+ {
+ entry->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ entry->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ entry->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ entry->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+ {
+ entry->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (gtt_storage_local_hash == NULL)
+ return false;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_FIND, &found);
+
+ if (!found)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ if (relpages)
+ *relpages = entry->relpages;
+
+ if (reltuples)
+ *reltuples = entry->reltuples;
+
+ if (relallvisible)
+ *relallvisible = entry->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = entry->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = entry->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ /* todo */
+ if (entry->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ Assert(entry->relid == reloid);
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == 0)
+ {
+ entry->attnum[i] = attnum;
+ break;
+ }
+ else if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < entry->natts);
+ Assert(entry->att_stat_tups[i] == NULL);
+ entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ bool found;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &reloid, HASH_FIND, &found);
+
+ if (!found)
+ return NULL;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ return entry->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+ MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ HeapTuple tuple;
+ Relation rel = NULL;
+ int attnum = PG_GETARG_INT32(1);
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Tuplestorestate *tupstore;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ tuple = get_gtt_att_statistic(reloid, attnum, false);
+ if (tuple)
+ {
+ Datum values[31];
+ bool isnull[31];
+ HeapTuple res = NULL;
+
+ memset(&values, 0, sizeof(values));
+ memset(&isnull, 0, sizeof(isnull));
+ heap_deform_tuple(tuple, sd, values, isnull);
+ res = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, res);
+ }
+
+ table_close(rel, NoLock);
+ table_close(pg_tatistic, AccessShareLock);
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ if (get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid))
+ {
+ Datum values[5];
+ bool isnull[5];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(relpages);
+ values[1] = Float4GetDatum((float4)reltuples);
+ values[2] = Int32GetDatum(relallvisible);
+ values[3] = UInt32GetDatum(relfrozenxid);
+ values[4] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = table_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ table_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ map = copy_active_gtt_bitmap(rel->rd_node);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ table_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ int i = 0;
+ int j = 0;
+ uint32 oldest = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ if (max_active_gtt <= 0)
+ return (Datum) 0;
+
+ if (RecoveryInProgress())
+ return (Datum) 0;
+
+ pids = palloc0(sizeof(int) * num_xid);
+ xids = palloc0(sizeof(int) * num_xid);
+ oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+ if (i > 0)
+ {
+ if (i > 0)
+ {
+ pids[i] = 0;
+ xids[i] = oldest;
+ i++;
+ }
+
+ for(j = 0; j < i; j++)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(pids[j]);
+ values[1] = UInt32GetDatum(xids[j]);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ }
+ tuplestore_donestoring(tupstore);
+ pfree(pids);
+ pfree(xids);
+
+ return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(indexOid));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid relOid = index->rd_index->indrelid;
+
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ if (gtt_storage_attached(relOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ int i;
+ Oid toastrelid;
+
+ if (operation != CMD_INSERT)
+ return;
+
+ if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+ return;
+
+ if (!RELATION_IS_GLOBAL_TEMP(relation))
+ return;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_attached(relation->rd_node.relNode))
+ return;
+
+ RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+ for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+ {
+ Relation index = resultRelInfo->ri_IndexRelationDescs[i];
+ IndexInfo *info = resultRelInfo->ri_IndexRelationInfo[i];
+
+ Assert(index->rd_index->indisvalid);
+ Assert(index->rd_index->indislive);
+ Assert(index->rd_index->indisready);
+
+ index_build(relation, index, info, true, false);
+ }
+
+ toastrelid = relation->rd_rel->reltoastrelid;
+ if (OidIsValid(toastrelid))
+ {
+ Relation toastrel;
+ ListCell *indlist;
+
+ toastrel = table_open(toastrelid, RowExclusiveLock);
+ RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+ foreach(indlist, RelationGetIndexList(toastrel))
+ {
+ Oid indexId = lfirst_oid(indlist);
+ Relation currentIndex;
+ IndexInfo *indexInfo;
+
+ currentIndex = index_open(indexId, RowExclusiveLock);
+
+ indexInfo = BuildDummyIndexInfo(currentIndex);
+ index_build(toastrel, currentIndex, indexInfo, true, false);
+ index_close(currentIndex, NoLock);
+ }
+
+ table_close(toastrel, NoLock);
+ }
+
+ return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46..491db16 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
WHERE NOT pg_is_other_temp_schema(N.oid)
AND relkind = 'S';
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stavalues1
+ WHEN s.stakind2 = 1 THEN s.stavalues2
+ WHEN s.stakind3 = 1 THEN s.stavalues3
+ WHEN s.stakind4 = 1 THEN s.stavalues4
+ WHEN s.stakind5 = 1 THEN s.stavalues5
+ END AS most_common_vals,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stanumbers1
+ WHEN s.stakind2 = 1 THEN s.stanumbers2
+ WHEN s.stakind3 = 1 THEN s.stanumbers3
+ WHEN s.stakind4 = 1 THEN s.stanumbers4
+ WHEN s.stakind5 = 1 THEN s.stanumbers5
+ END AS most_common_freqs,
+ CASE
+ WHEN s.stakind1 = 2 THEN s.stavalues1
+ WHEN s.stakind2 = 2 THEN s.stavalues2
+ WHEN s.stakind3 = 2 THEN s.stavalues3
+ WHEN s.stakind4 = 2 THEN s.stavalues4
+ WHEN s.stakind5 = 2 THEN s.stavalues5
+ END AS histogram_bounds,
+ CASE
+ WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+ WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+ WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+ WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+ WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+ END AS correlation,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stavalues1
+ WHEN s.stakind2 = 4 THEN s.stavalues2
+ WHEN s.stakind3 = 4 THEN s.stavalues3
+ WHEN s.stakind4 = 4 THEN s.stavalues4
+ WHEN s.stakind5 = 4 THEN s.stavalues5
+ END AS most_common_elems,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stanumbers1
+ WHEN s.stakind2 = 4 THEN s.stanumbers2
+ WHEN s.stakind3 = 4 THEN s.stanumbers3
+ WHEN s.stakind4 = 4 THEN s.stanumbers4
+ WHEN s.stakind5 = 4 THEN s.stanumbers5
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN s.stakind1 = 5 THEN s.stanumbers1
+ WHEN s.stakind2 = 5 THEN s.stanumbers2
+ WHEN s.stakind3 = 5 THEN s.stanumbers3
+ WHEN s.stakind4 = 5 THEN s.stanumbers4
+ WHEN s.stakind5 = 5 THEN s.stanumbers5
+ END AS elem_count_histogram
+ FROM
+ pg_class c
+ JOIN pg_attribute a ON c.oid = a.attrelid
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
CREATE VIEW pg_stats WITH (security_barrier) AS
SELECT
nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..7efc5a9 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
#include "commands/dbcommands.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int acquire_inherited_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
static void update_attstats(Oid relid, bool inh,
- int natts, VacAttrStats **vacattrstats);
+ int natts, VacAttrStats **vacattrstats, char relpersistence);
static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
return;
}
+ if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+ !gtt_storage_attached(onerel->rd_node.relNode))
+ {
+ relation_close(onerel, ShareUpdateExclusiveLock);
+ return;
+ }
+
/*
* We can ANALYZE any table except pg_statistic. See update_attstats
*/
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* pg_statistic for columns we didn't process, we leave them alone.)
*/
update_attstats(RelationGetRelid(onerel), inh,
- attr_cnt, vacattrstats);
+ attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
for (ind = 0; ind < nindexes; ind++)
{
AnlIndexData *thisdata = &indexdata[ind];
update_attstats(RelationGetRelid(Irel[ind]), false,
- thisdata->attr_cnt, thisdata->vacattrstats);
+ thisdata->attr_cnt, thisdata->vacattrstats,
+ RelationGetRelPersistence(Irel[ind]));
}
/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
* by taking a self-exclusive lock on the relation in analyze_rel().
*/
static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
{
Relation sd;
int attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
}
}
- /* Is there already a pg_statistic tuple for this attribute? */
- oldtup = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(relid),
- Int16GetDatum(stats->attr->attnum),
- BoolGetDatum(inh));
-
- if (HeapTupleIsValid(oldtup))
+ /* Update column statistic to localhash, not catalog */
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
{
- /* Yes, replace it */
- stup = heap_modify_tuple(oldtup,
- RelationGetDescr(sd),
- values,
- nulls,
- replaces);
- ReleaseSysCache(oldtup);
- CatalogTupleUpdate(sd, &stup->t_self, stup);
+ up_gtt_att_statistic(relid,
+ stats->attr->attnum,
+ inh,
+ natts,
+ RelationGetDescr(sd),
+ values,
+ nulls);
}
else
{
- /* No, insert new tuple */
- stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
- CatalogTupleInsert(sd, stup);
- }
+ /* Is there already a pg_statistic tuple for this attribute? */
+ oldtup = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(relid),
+ Int16GetDatum(stats->attr->attnum),
+ BoolGetDatum(inh));
- heap_freetuple(stup);
+ if (HeapTupleIsValid(oldtup))
+ {
+ /* Yes, replace it */
+ stup = heap_modify_tuple(oldtup,
+ RelationGetDescr(sd),
+ values,
+ nulls,
+ replaces);
+ ReleaseSysCache(oldtup);
+ CatalogTupleUpdate(sd, &stup->t_self, stup);
+ }
+ else
+ {
+ /* No, insert new tuple */
+ stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+ CatalogTupleInsert(sd, stup);
+ }
+
+ heap_freetuple(stup);
+ }
}
table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
errmsg("cannot vacuum temporary tables of other sessions")));
}
+ /* not support cluster global temp table yet */
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("not support cluster global temporary tables yet")));
+
/*
* Also check for active uses of the relation in the current transaction,
* including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 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;
+
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
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
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
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
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
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
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 MAPPINGpostgres=# 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 TABLEpostgres=# 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/>
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
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 MAPPINGpostgres=# 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 TABLEpostgres=# 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/>
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 MAPPINGpostgres=# 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 TABLEpostgres=# 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/>
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
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 tableTable 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 MAPPINGpostgres=# 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 TABLEpostgres=# 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
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 MAPPINGpostgres=# 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 TABLEpostgres=# 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/>--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/ <https://www.enterprisedb.com/>
The Enterprise PostgreSQL Company
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 tableTable 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 MAPPINGpostgres=# 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 TABLEpostgres=# 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/>--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/ <https://www.enterprisedb.com/>
The Enterprise PostgreSQL Company
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
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/>
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/>
The Enterprise PostgreSQL Company--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_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(>t_shared_ctl->lock);
+
+ if (isCommit)
+ {
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ }
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_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(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_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(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = 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(>t_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(>t_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;
+
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 logs123waiting 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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ info.keysize = sizeof(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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!skiplock)
+ LWLockAcquire(>t_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(>t_shared_ctl->lock);
+
+ if (isCommit)
+ {
+ elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+ rnode.dbNode, rnode.spcNode, rnode.relNode);
+ }
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ LWLockAcquire(>t_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(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ LWLockAcquire(>t_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(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = 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(>t_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(>t_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;
+
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 SEQUENCEpostgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
CREATE TABLEpostgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
CREATE TABLEpostgres=# alter table gtt1 add c2 int default nextval('seq');
ERROR: cannot reindex global temporary tablespostgres=# 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/>
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 TABLEpostgres=# 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 TABLEpostgres=# 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 TABLEIn "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/>
Attachments:
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 TABLEThe 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
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 SEQUENCEpostgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 int PRIMARY KEY) ON
COMMIT DELETE ROWS;
CREATE TABLEpostgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 int PRIMARY KEY) ON
COMMIT PRESERVE ROWS;
CREATE TABLEpostgres=# alter table gtt1 add c2 int default nextval('seq');
ERROR: cannot reindex global temporary tablespostgres=# alter table gtt2 add c2 int default nextval('seq');
ERROR: cannot reindex global temporary tablesreindex 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
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
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
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=falsepostgres=# 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
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
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=falsepostgres=# 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/>
Attachments:
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 16384TestCase2:
-- 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 TABLEI 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 useOn 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 SEQUENCEpostgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
CREATE TABLEpostgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
CREATE TABLEpostgres=# alter table gtt1 add c2 int default nextval('seq');
ERROR: cannot reindex global temporary tablespostgres=# alter table gtt2 add c2 int default nextval('seq');
ERROR: cannot reindex global temporary tablesreindex 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/>--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = 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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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;
+
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 TABLEpostgres=# create index fff on co(n);
CREATE INDEXCase 1-
postgres=# reindex table co;
REINDEXCase -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:
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:
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
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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = 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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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;
+
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
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
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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = 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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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;
+
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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = 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(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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;
+
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
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/>
The Enterprise PostgreSQL Company--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/>
Attachments:
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
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
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 TABLEpostgres=# 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
č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 usepostgres=# 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 tableTo drop the GTT from any session, we need to exit from all other sessions.
postgres=# drop table gtt2 ;
DROP TABLEKindly 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 TABLEpostgres=# 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
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 table3. 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 table4. 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 TABLECase2: 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 usepostgres=# 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 tableTo drop the GTT from any session, we need to exit from all other sessions.
postgres=# drop table gtt2 ;
DROP TABLEKindly 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 TABLEpostgres=# create materialized view mvw as select * from gtt;
ERROR: materialized views must not use global temporary tables or viewsAnyways 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/>--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/>
Attachments:
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 usepostgres=# 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 tableTo drop the GTT from any session, we need to exit from all other sessions.
postgres=# drop table gtt2 ;
DROP TABLEKindly 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 TABLEpostgres=# 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
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
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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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,
+ >t_relfrozenxid, >t_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;
+
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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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,
+ >t_relfrozenxid, >t_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;
+
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
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
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
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/>
Attachments:
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
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.
VACUUMThis 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
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
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.
VACUUMThis 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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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,
+ >t_relfrozenxid, >t_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;
+
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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,
+ >t_relfrozenxid, >t_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;
+
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:
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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,
+ >t_relfrozenxid, >t_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;
+
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
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 postgresPostgreSQL 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, exitinglog 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:
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 modeI 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
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 sessionThe 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
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 sessionThe 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 ;
VACUUMY 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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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,
+ >t_relfrozenxid, >t_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;
+
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
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
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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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,
+ >t_relfrozenxid, >t_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;
+
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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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,
+ >t_relfrozenxid, >t_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;
+
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/>
Attachments:
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
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
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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,
+ >t_relfrozenxid, >t_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;
+
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 table3. 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 table4. 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 TABLECase2: 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 usepostgres=# 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 tableTo drop the GTT from any session, we need to exit from all other sessions.
postgres=# drop table gtt2 ;
DROP TABLEKindly 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 TABLEpostgres=# create materialized view mvw as select * from gtt;
ERROR: materialized views must not use global temporary tables or viewsAnyways 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/>--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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,
+ >t_relfrozenxid, >t_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;
+
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 usepostgres=# 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 tableTo drop the GTT from any session, we need to exit from all other sessions.
postgres=# drop table gtt2 ;
DROP TABLEKindly 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 TABLEpostgres=# 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
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;
CLUSTERpostgres=# 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;
CLUSTERIn 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 SahuWenjing
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 usepostgres=# 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 tableTo drop the GTT from any session, we need to exit from all other
sessions.
postgres=# drop table gtt2 ;
DROP TABLEKindly 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 TABLEpostgres=# 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
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;
CLUSTERpostgres=# 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;
CLUSTERIn 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 SahuWenjing
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 table3. 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 table4. 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 TABLECase2: 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 usepostgres=# 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 tableTo drop the GTT from any session, we need to exit from all other sessions.
postgres=# drop table gtt2 ;
DROP TABLEKindly 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 TABLEpostgres=# create materialized view mvw as select * from gtt;
ERROR: materialized views must not use global temporary tables or viewsAnyways 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/>--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/>--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/>
Attachments:
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;
CLUSTERpostgres=# 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;
CLUSTERIn 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 SahuWenjing
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 table3. 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 table4. 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 TABLECase2: 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 usepostgres=# 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 tableTo drop the GTT from any session, we need to exit from all other sessions.
postgres=# drop table gtt2 ;
DROP TABLEKindly 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 TABLEpostgres=# create materialized view mvw as select * from gtt;
ERROR: materialized views must not use global temporary tables or viewsAnyways 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/>--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/>--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/>
Attachments:
č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;
CLUSTERpostgres=# 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;
CLUSTERIn 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 SahuWenjing
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 usepostgres=# 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 tableTo drop the GTT from any session, we need to exit from all other
sessions.
postgres=# drop table gtt2 ;
DROP TABLEKindly 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 TABLEpostgres=# 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
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
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
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
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;
COMMITyes, 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;
CLUSTERpostgres=# 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;
CLUSTERIn 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 SahuWenjing
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 table3. 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 table4. 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 TABLECase2: 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 usepostgres=# 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 tableTo drop the GTT from any session, we need to exit from all other sessions.
postgres=# drop table gtt2 ;
DROP TABLEKindly 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 TABLEpostgres=# create materialized view mvw as select * from gtt;
ERROR: materialized views must not use global temporary tables or viewsAnyways 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/>--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/>--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/>
Attachments:
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
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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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,
+ >t_relfrozenxid, >t_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;
+
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/>--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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,
+ >t_relfrozenxid, >t_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;
+
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:
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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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,
+ >t_relfrozenxid, >t_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;
+
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 v32wenjing
--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com
--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com
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 v32wenjing
--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/>--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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,
+ >t_relfrozenxid, >t_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;
+
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
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 TABLEY Terminal -
postgres=# create index index12 on f(n);
CREATE INDEX
postgres=# \qX terminal -
postgres=# reindex index index12;
REINDEX
postgres=# cluster f using index12;
ERROR: cannot cluster on invalid index "index12"
postgres=# drop index index12;
DROP INDEXif 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:
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
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/>
Attachments:
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
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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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,
+ >t_relfrozenxid, >t_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;
+
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
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/>
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
ANALYZEThis 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/>
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
ANALYZEThis 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
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
ANALYZEThis 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
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 allI 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
ANALYZEThis 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/>
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
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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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,
+ >t_relfrozenxid, >t_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;
+
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
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
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/>
EMAIL: mailto:movead(dot)li(at)highgo(dot)ca
Attachments:
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:
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
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/>
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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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,
+ >t_relfrozenxid, >t_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;
+
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->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,
+ >t_relfrozenxid, >t_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;
+
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
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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (!OidIsValid(relid))
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Oid relid,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!OidIsValid(relid))
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return;
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return;
+
+ if (num_pages > 0 &&
+ gtt_rnode->relpages != (int32)num_pages)
+ gtt_rnode->relpages = (int32)num_pages;
+
+ if (num_tuples > 0 &&
+ gtt_rnode->reltuples != (float4)num_tuples)
+ gtt_rnode->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (num_all_visible_pages > 0 &&
+ gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+ {
+ gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ gtt_rnode->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+ gtt_rnode->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ gtt_rnode->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+ {
+ gtt_rnode->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return false;
+
+ if (relpages)
+ *relpages = gtt_rnode->relpages;
+
+ if (reltuples)
+ *reltuples = gtt_rnode->reltuples;
+
+ if (relallvisible)
+ *relallvisible = gtt_rnode->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = gtt_rnode->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = gtt_rnode->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(reloid, true);
+ if (entry == NULL)
+ return;
+
+ if (entry->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ Assert(entry->relid == reloid);
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == 0)
+ {
+ entry->attnum[i] = attnum;
+ break;
+ }
+ else if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < entry->natts);
+ Assert(entry->att_stat_tups[i] == NULL);
+ entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ entry = gtt_search_by_relid(reloid, true);
+ if (entry == NULL)
+ return NULL;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ return entry->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+ MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ HeapTuple tuple;
+ Relation rel = NULL;
+ int attnum = PG_GETARG_INT32(1);
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Tuplestorestate *tupstore;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ tuple = get_gtt_att_statistic(reloid, attnum, false);
+ if (tuple)
+ {
+ Datum values[31];
+ bool isnull[31];
+ HeapTuple res = NULL;
+
+ memset(&values, 0, sizeof(values));
+ memset(&isnull, 0, sizeof(isnull));
+ heap_deform_tuple(tuple, sd, values, isnull);
+ res = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, res);
+ }
+
+ relation_close(rel, NoLock);
+ relation_close(pg_tatistic, AccessShareLock);
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ Oid relnode = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid);
+ relnode = gtt_fetch_current_relfilenode(reloid);
+ if (relnode != InvalidOid)
+ {
+ Datum values[6];
+ bool isnull[6];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(relnode);
+ values[1] = Int32GetDatum(relpages);
+ values[2] = Float4GetDatum((float4)reltuples);
+ values[3] = Int32GetDatum(relallvisible);
+ values[4] = UInt32GetDatum(relfrozenxid);
+ values[5] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+
+ tuplestore_donestoring(tupstore);
+ relation_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ map = copy_active_gtt_bitmap(reloid);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ relation_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ int i = 0;
+ int j = 0;
+ uint32 oldest = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ if (max_active_gtt <= 0)
+ return (Datum) 0;
+
+ if (RecoveryInProgress())
+ return (Datum) 0;
+
+ pids = palloc0(sizeof(int) * num_xid);
+ xids = palloc0(sizeof(int) * num_xid);
+ oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+ if (i > 0)
+ {
+ if (i > 0)
+ {
+ pids[i] = 0;
+ xids[i] = oldest;
+ i++;
+ }
+
+ for(j = 0; j < i; j++)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(pids[j]);
+ values[1] = UInt32GetDatum(xids[j]);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ }
+ tuplestore_donestoring(tupstore);
+ pfree(pids);
+ pfree(xids);
+
+ return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(RelationGetRelid(index)));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+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,
+ >t_relfrozenxid, >t_relminmxid);
+
+ if (TransactionIdIsValid(gtt_relfrozenxid) &&
+ TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+ FreezeXid = gtt_relfrozenxid;
+
+ if (MultiXactIdIsValid(gtt_relminmxid) &&
+ MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+ MultiXactCutoff = gtt_relminmxid;
+ }
+ else
+ {
+ /*
+ * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+ * backwards, so take the max.
+ */
+ if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+ TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+ FreezeXid = OldHeap->rd_rel->relfrozenxid;
- /*
- * MultiXactCutoff, similarly, shouldn't go backwards either.
- */
- if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
- MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
- MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+ /*
+ * MultiXactCutoff, similarly, shouldn't go backwards either.
+ */
+ if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+ MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+ MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+ }
/*
* Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -911,6 +954,15 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
table_close(OldHeap, NoLock);
table_close(NewHeap, NoLock);
+ /* Update relstats of global temporary table to localhash. */
+ if (is_gtt)
+ {
+ up_gtt_relstats(RelationGetRelid(NewHeap), num_pages, num_tuples, 0,
+ InvalidTransactionId, InvalidMultiXactId);
+ CommandCounterIncrement();
+ return;
+ }
+
/* Update pg_class to reflect the correct values of pages and tuples. */
relRelation = table_open(RelationRelationId, RowExclusiveLock);
@@ -1346,10 +1398,22 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
* Swap the contents of the heap relations (including any toast tables).
* Also set old heap's relfrozenxid to frozenXid.
*/
- swap_relation_files(OIDOldHeap, OIDNewHeap,
+ if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ Assert(!is_system_catalog);
+ /* For global temporary table modify data in localhash, not pg_class */
+ gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+ (OIDOldHeap == RelationRelationId),
+ swap_toast_by_content, is_internal,
+ frozenXid, cutoffMulti, mapped_tables);
+ }
+ else
+ {
+ swap_relation_files(OIDOldHeap, OIDNewHeap,
(OIDOldHeap == RelationRelationId),
swap_toast_by_content, is_internal,
frozenXid, cutoffMulti, mapped_tables);
+ }
/*
* If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1557,3 +1621,146 @@ get_tables_to_cluster(MemoryContext cluster_context)
return rvs;
}
+
+/*
+ * For global temporary table, storage information is stored in localhash,
+ * This function like swap_relation_files except that update storage information,
+ * in the localhash, not pg_class.
+ */
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+ bool swap_toast_by_content,
+ bool is_internal,
+ TransactionId frozenXid,
+ MultiXactId cutoffMulti,
+ Oid *mapped_tables)
+{
+ Relation relRelation;
+ Oid relfilenode1,
+ relfilenode2;
+ Relation rel1;
+ Relation rel2;
+
+ relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+ rel1 = relation_open(r1, AccessExclusiveLock);
+ rel2 = relation_open(r2, AccessExclusiveLock);
+
+ relfilenode1 = gtt_fetch_current_relfilenode(r1);
+ relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+ Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+ gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+ CacheInvalidateRelcache(rel1);
+ CacheInvalidateRelcache(rel2);
+
+ InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+ InvalidOid, is_internal);
+ InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+ InvalidOid, true);
+
+ if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+ {
+ if (swap_toast_by_content)
+ {
+ if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+ {
+ gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+ rel2->rd_rel->reltoastrelid,
+ target_is_pg_class,
+ swap_toast_by_content,
+ is_internal,
+ frozenXid,
+ cutoffMulti,
+ mapped_tables);
+ }
+ else
+ elog(ERROR, "cannot swap toast files by content when there's only one");
+ }
+ else
+ {
+ ObjectAddress baseobject,
+ toastobject;
+ long count;
+
+ if (IsSystemRelation(rel1))
+ elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+ if (rel1->rd_rel->reltoastrelid)
+ {
+ count = deleteDependencyRecordsFor(RelationRelationId,
+ rel1->rd_rel->reltoastrelid,
+ false);
+ if (count != 1)
+ elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+ count);
+ }
+ if (rel2->rd_rel->reltoastrelid)
+ {
+ count = deleteDependencyRecordsFor(RelationRelationId,
+ rel2->rd_rel->reltoastrelid,
+ false);
+ if (count != 1)
+ elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+ count);
+ }
+
+ /* Register new dependencies */
+ baseobject.classId = RelationRelationId;
+ baseobject.objectSubId = 0;
+ toastobject.classId = RelationRelationId;
+ toastobject.objectSubId = 0;
+
+ if (rel1->rd_rel->reltoastrelid)
+ {
+ baseobject.objectId = r1;
+ toastobject.objectId = rel1->rd_rel->reltoastrelid;
+ recordDependencyOn(&toastobject, &baseobject,
+ DEPENDENCY_INTERNAL);
+ }
+
+ if (rel2->rd_rel->reltoastrelid)
+ {
+ baseobject.objectId = r2;
+ toastobject.objectId = rel2->rd_rel->reltoastrelid;
+ recordDependencyOn(&toastobject, &baseobject,
+ DEPENDENCY_INTERNAL);
+ }
+ }
+ }
+
+ if (swap_toast_by_content &&
+ rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+ rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+ {
+ Oid toastIndex1,
+ toastIndex2;
+
+ /* Get valid index for each relation */
+ toastIndex1 = toast_get_valid_index(r1,
+ AccessExclusiveLock);
+ toastIndex2 = toast_get_valid_index(r2,
+ AccessExclusiveLock);
+
+ gtt_swap_relation_files(toastIndex1,
+ toastIndex2,
+ target_is_pg_class,
+ swap_toast_by_content,
+ is_internal,
+ InvalidTransactionId,
+ InvalidMultiXactId,
+ mapped_tables);
+ }
+
+ relation_close(rel1, NoLock);
+ relation_close(rel2, NoLock);
+
+ table_close(relRelation, RowExclusiveLock);
+
+ RelationCloseSmgrByOid(r1);
+ RelationCloseSmgrByOid(r2);
+
+ CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 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;
+
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
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 loand 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 INDEXclose 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/>
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (!OidIsValid(relid))
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Oid relid,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!OidIsValid(relid))
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return;
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return;
+
+ if (num_pages > 0 &&
+ gtt_rnode->relpages != (int32)num_pages)
+ gtt_rnode->relpages = (int32)num_pages;
+
+ if (num_tuples > 0 &&
+ gtt_rnode->reltuples != (float4)num_tuples)
+ gtt_rnode->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (num_all_visible_pages > 0 &&
+ gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+ {
+ gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ gtt_rnode->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+ gtt_rnode->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ gtt_rnode->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+ {
+ gtt_rnode->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return false;
+
+ if (relpages)
+ *relpages = gtt_rnode->relpages;
+
+ if (reltuples)
+ *reltuples = gtt_rnode->reltuples;
+
+ if (relallvisible)
+ *relallvisible = gtt_rnode->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = gtt_rnode->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = gtt_rnode->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(reloid, true);
+ if (entry == NULL)
+ return;
+
+ if (entry->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ Assert(entry->relid == reloid);
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == 0)
+ {
+ entry->attnum[i] = attnum;
+ break;
+ }
+ else if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < entry->natts);
+ Assert(entry->att_stat_tups[i] == NULL);
+ entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ entry = gtt_search_by_relid(reloid, true);
+ if (entry == NULL)
+ return NULL;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ return entry->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+ MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ HeapTuple tuple;
+ Relation rel = NULL;
+ int attnum = PG_GETARG_INT32(1);
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Tuplestorestate *tupstore;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ tuple = get_gtt_att_statistic(reloid, attnum, false);
+ if (tuple)
+ {
+ Datum values[31];
+ bool isnull[31];
+ HeapTuple res = NULL;
+
+ memset(&values, 0, sizeof(values));
+ memset(&isnull, 0, sizeof(isnull));
+ heap_deform_tuple(tuple, sd, values, isnull);
+ res = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, res);
+ }
+
+ relation_close(rel, NoLock);
+ relation_close(pg_tatistic, AccessShareLock);
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ Oid relnode = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid);
+ relnode = gtt_fetch_current_relfilenode(reloid);
+ if (relnode != InvalidOid)
+ {
+ Datum values[6];
+ bool isnull[6];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(relnode);
+ values[1] = Int32GetDatum(relpages);
+ values[2] = Float4GetDatum((float4)reltuples);
+ values[3] = Int32GetDatum(relallvisible);
+ values[4] = UInt32GetDatum(relfrozenxid);
+ values[5] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+
+ tuplestore_donestoring(tupstore);
+ relation_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ map = copy_active_gtt_bitmap(reloid);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ relation_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ int i = 0;
+ int j = 0;
+ uint32 oldest = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ if (max_active_gtt <= 0)
+ return (Datum) 0;
+
+ if (RecoveryInProgress())
+ return (Datum) 0;
+
+ pids = palloc0(sizeof(int) * num_xid);
+ xids = palloc0(sizeof(int) * num_xid);
+ oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+ if (i > 0)
+ {
+ if (i > 0)
+ {
+ pids[i] = 0;
+ xids[i] = oldest;
+ i++;
+ }
+
+ for(j = 0; j < i; j++)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = Int32GetDatum(pids[j]);
+ values[1] = UInt32GetDatum(xids[j]);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ }
+ tuplestore_donestoring(tupstore);
+ pfree(pids);
+ pfree(xids);
+
+ return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(RelationGetRelid(index)));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the state of the global temporary table's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid heapOid = index->rd_index->indrelid;
+
+ /* Must be global temporary table */
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ /*
+ * If this global temporary table is not initialized in the current Backend,
+ * its index status is temporarily set to invalid(local relcache).
+ */
+ if (gtt_storage_attached(heapOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ int i;
+ Oid toastrelid;
+
+ if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+ return;
+
+ if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+ return;
+
+ if (!RELATION_IS_GLOBAL_TEMP(relation))
+ return;
+
+ if (gtt_storage_attached(RelationGetRelid(relation)))
+ return;
+
+ RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+ for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+ {
+ Relation index = resultRelInfo->ri_IndexRelationDescs[i];
+ IndexInfo *info = resultRelInfo->ri_IndexRelationInfo[i];
+
+ Assert(index->rd_index->indisvalid);
+ Assert(index->rd_index->indislive);
+ Assert(index->rd_index->indisready);
+
+ index_build(relation, index, info, true, false);
+ }
+
+ toastrelid = relation->rd_rel->reltoastrelid;
+ if (OidIsValid(toastrelid))
+ {
+ Relation toastrel;
+ ListCell *indlist;
+
+ toastrel = table_open(toastrelid, RowExclusiveLock);
+ RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+ foreach(indlist, RelationGetIndexList(toastrel))
+ {
+ Oid indexId = lfirst_oid(indlist);
+ Relation currentIndex;
+ IndexInfo *indexInfo;
+
+ currentIndex = index_open(indexId, RowExclusiveLock);
+
+ indexInfo = BuildDummyIndexInfo(currentIndex);
+ index_build(toastrel, currentIndex, indexInfo, true, false);
+ index_close(currentIndex, NoLock);
+ }
+
+ table_close(toastrel, NoLock);
+ }
+
+ return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+ int i;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->att_stat_tups[i])
+ {
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ }
+
+ entry->attnum[i] = 0;
+ }
+
+ return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+ int i;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->att_stat_tups[i])
+ {
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ }
+ }
+
+ if (entry->attnum)
+ pfree(entry->attnum);
+
+ if (entry->att_stat_tups)
+ pfree(entry->att_stat_tups);
+
+ return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return InvalidOid;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return InvalidOid;
+
+ Assert(entry->relid == relid);
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return InvalidOid;
+
+ return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+ gtt_local_hash_entry *entry1;
+ gtt_local_hash_entry *entry2;
+ gtt_relfilenode *gtt_rnode1 = NULL;
+ gtt_relfilenode *gtt_rnode2 = NULL;
+ MemoryContext oldcontext;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry1 = gtt_search_by_relid(rel1, false);
+ gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+ entry2 = gtt_search_by_relid(rel2, false);
+ gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+ entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+ entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+ entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+ MemoryContextSwitchTo(oldcontext);
+
+ if (footprint)
+ {
+ entry1->oldrelid = rel2;
+ entry2->oldrelid = rel1;
+ }
+
+ return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry *entry, Oid relfilenode, bool missing_ok)
+{
+ gtt_relfilenode *rnode = NULL;
+ ListCell *lc;
+
+ Assert(entry);
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+ if (gtt_rnode->relfilenode == relfilenode)
+ {
+ rnode = gtt_rnode;
+ break;
+ }
+ }
+
+ if (!missing_ok && rnode == NULL)
+ elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+ return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+ gtt_local_hash_entry *entry = NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, NULL);
+
+ if (entry == NULL && !missing_ok)
+ elog(ERROR, "relid %u not found in local hash", relid);
+
+ return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2e4aa1c..6018192 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
WHERE NOT pg_is_other_temp_schema(N.oid)
AND relkind = 'S';
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stavalues1
+ WHEN s.stakind2 = 1 THEN s.stavalues2
+ WHEN s.stakind3 = 1 THEN s.stavalues3
+ WHEN s.stakind4 = 1 THEN s.stavalues4
+ WHEN s.stakind5 = 1 THEN s.stavalues5
+ END AS most_common_vals,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stanumbers1
+ WHEN s.stakind2 = 1 THEN s.stanumbers2
+ WHEN s.stakind3 = 1 THEN s.stanumbers3
+ WHEN s.stakind4 = 1 THEN s.stanumbers4
+ WHEN s.stakind5 = 1 THEN s.stanumbers5
+ END AS most_common_freqs,
+ CASE
+ WHEN s.stakind1 = 2 THEN s.stavalues1
+ WHEN s.stakind2 = 2 THEN s.stavalues2
+ WHEN s.stakind3 = 2 THEN s.stavalues3
+ WHEN s.stakind4 = 2 THEN s.stavalues4
+ WHEN s.stakind5 = 2 THEN s.stavalues5
+ END AS histogram_bounds,
+ CASE
+ WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+ WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+ WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+ WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+ WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+ END AS correlation,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stavalues1
+ WHEN s.stakind2 = 4 THEN s.stavalues2
+ WHEN s.stakind3 = 4 THEN s.stavalues3
+ WHEN s.stakind4 = 4 THEN s.stavalues4
+ WHEN s.stakind5 = 4 THEN s.stavalues5
+ END AS most_common_elems,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stanumbers1
+ WHEN s.stakind2 = 4 THEN s.stanumbers2
+ WHEN s.stakind3 = 4 THEN s.stanumbers3
+ WHEN s.stakind4 = 4 THEN s.stanumbers4
+ WHEN s.stakind5 = 4 THEN s.stanumbers5
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN s.stakind1 = 5 THEN s.stanumbers1
+ WHEN s.stakind2 = 5 THEN s.stanumbers2
+ WHEN s.stakind3 = 5 THEN s.stanumbers3
+ WHEN s.stakind4 = 5 THEN s.stanumbers4
+ WHEN s.stakind5 = 5 THEN s.stanumbers5
+ END AS elem_count_histogram
+ FROM
+ pg_class c
+ JOIN pg_attribute a ON c.oid = a.attrelid
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
CREATE VIEW pg_stats WITH (security_barrier) AS
SELECT
nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 8af12b5..a0b6390 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
#include "commands/dbcommands.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int acquire_inherited_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
static void update_attstats(Oid relid, bool inh,
- int natts, VacAttrStats **vacattrstats);
+ int natts, VacAttrStats **vacattrstats, char relpersistence);
static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
}
/*
+ * Skip the global temporary table that did not initialize the storage
+ * in this backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+ !gtt_storage_attached(RelationGetRelid(onerel)))
+ {
+ relation_close(onerel, ShareUpdateExclusiveLock);
+ return;
+ }
+
+ /*
* We can ANALYZE any table except pg_statistic. See update_attstats
*/
if (RelationGetRelid(onerel) == StatisticRelationId)
@@ -575,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* pg_statistic for columns we didn't process, we leave them alone.)
*/
update_attstats(RelationGetRelid(onerel), inh,
- attr_cnt, vacattrstats);
+ attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
for (ind = 0; ind < nindexes; ind++)
{
AnlIndexData *thisdata = &indexdata[ind];
update_attstats(RelationGetRelid(Irel[ind]), false,
- thisdata->attr_cnt, thisdata->vacattrstats);
+ thisdata->attr_cnt, thisdata->vacattrstats,
+ RelationGetRelPersistence(Irel[ind]));
}
/*
@@ -1445,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
* by taking a self-exclusive lock on the relation in analyze_rel().
*/
static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
{
Relation sd;
int attno;
@@ -1547,31 +1560,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
}
}
- /* Is there already a pg_statistic tuple for this attribute? */
- oldtup = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(relid),
- Int16GetDatum(stats->attr->attnum),
- BoolGetDatum(inh));
-
- if (HeapTupleIsValid(oldtup))
+ /*
+ * For global temporary table,
+ * Update column statistic to localhash, not catalog.
+ */
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
{
- /* Yes, replace it */
- stup = heap_modify_tuple(oldtup,
- RelationGetDescr(sd),
- values,
- nulls,
- replaces);
- ReleaseSysCache(oldtup);
- CatalogTupleUpdate(sd, &stup->t_self, stup);
+ up_gtt_att_statistic(relid,
+ stats->attr->attnum,
+ inh,
+ natts,
+ RelationGetDescr(sd),
+ values,
+ nulls);
}
else
{
- /* No, insert new tuple */
- stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
- CatalogTupleInsert(sd, stup);
- }
+ /* Is there already a pg_statistic tuple for this attribute? */
+ oldtup = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(relid),
+ Int16GetDatum(stats->attr->attnum),
+ BoolGetDatum(inh));
- heap_freetuple(stup);
+ if (HeapTupleIsValid(oldtup))
+ {
+ /* Yes, replace it */
+ stup = heap_modify_tuple(oldtup,
+ RelationGetDescr(sd),
+ values,
+ nulls,
+ replaces);
+ ReleaseSysCache(oldtup);
+ CatalogTupleUpdate(sd, &stup->t_self, stup);
+ }
+ else
+ {
+ /* No, insert new tuple */
+ stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+ CatalogTupleInsert(sd, stup);
+ }
+
+ heap_freetuple(stup);
+ }
}
table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 04d12a7..f83c1d9 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/progress.h"
@@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+ bool swap_toast_by_content,
+ bool is_internal,
+ TransactionId frozenXid,
+ MultiXactId cutoffMulti,
+ Oid *mapped_tables);
/*---------------------------------------------------------------------------
@@ -367,6 +374,18 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
}
/*
+ * Skip the global temporary table that did not initialize the storage
+ * in this backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+ !gtt_storage_attached(RelationGetRelid(OldHeap)))
+ {
+ relation_close(OldHeap, AccessExclusiveLock);
+ pgstat_progress_end_command();
+ return;
+ }
+
+ /*
* Also check for active uses of the relation in the current transaction,
* including open scans and pending AFTER trigger events.
*/
@@ -750,6 +769,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
BlockNumber num_pages;
int elevel = verbose ? INFO : DEBUG2;
PGRUsage ru0;
+ bool is_gtt = false;
+ uint32 gtt_relfrozenxid = 0;
+ uint32 gtt_relminmxid = 0;
pg_rusage_init(&ru0);
@@ -763,6 +785,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
else
OldIndex = NULL;
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+ is_gtt = true;
+
/*
* Their tuple descriptors should be exactly alike, but here we only need
* assume that they have the same number of columns.
@@ -830,20 +855,38 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
&OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
NULL);
- /*
- * FreezeXid will become the table's new relfrozenxid, and that mustn't go
- * backwards, so take the max.
- */
- if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
- TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
- FreezeXid = OldHeap->rd_rel->relfrozenxid;
+ if (is_gtt)
+ {
+ /* Gets transaction information for global temporary table from localhash. */
+ get_gtt_relstats(OIDOldHeap,
+ NULL, NULL, NULL,
+ >t_relfrozenxid, >t_relminmxid);
+
+ if (TransactionIdIsValid(gtt_relfrozenxid) &&
+ TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+ FreezeXid = gtt_relfrozenxid;
+
+ if (MultiXactIdIsValid(gtt_relminmxid) &&
+ MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+ MultiXactCutoff = gtt_relminmxid;
+ }
+ else
+ {
+ /*
+ * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+ * backwards, so take the max.
+ */
+ if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+ TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+ FreezeXid = OldHeap->rd_rel->relfrozenxid;
- /*
- * MultiXactCutoff, similarly, shouldn't go backwards either.
- */
- if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
- MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
- MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+ /*
+ * MultiXactCutoff, similarly, shouldn't go backwards either.
+ */
+ if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+ MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+ MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+ }
/*
* Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -911,6 +954,15 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
table_close(OldHeap, NoLock);
table_close(NewHeap, NoLock);
+ /* Update relstats of global temporary table to localhash. */
+ if (is_gtt)
+ {
+ up_gtt_relstats(RelationGetRelid(NewHeap), num_pages, num_tuples, 0,
+ InvalidTransactionId, InvalidMultiXactId);
+ CommandCounterIncrement();
+ return;
+ }
+
/* Update pg_class to reflect the correct values of pages and tuples. */
relRelation = table_open(RelationRelationId, RowExclusiveLock);
@@ -1346,10 +1398,22 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
* Swap the contents of the heap relations (including any toast tables).
* Also set old heap's relfrozenxid to frozenXid.
*/
- swap_relation_files(OIDOldHeap, OIDNewHeap,
+ if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ Assert(!is_system_catalog);
+ /* For global temporary table modify data in localhash, not pg_class */
+ gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+ (OIDOldHeap == RelationRelationId),
+ swap_toast_by_content, is_internal,
+ frozenXid, cutoffMulti, mapped_tables);
+ }
+ else
+ {
+ swap_relation_files(OIDOldHeap, OIDNewHeap,
(OIDOldHeap == RelationRelationId),
swap_toast_by_content, is_internal,
frozenXid, cutoffMulti, mapped_tables);
+ }
/*
* If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1557,3 +1621,146 @@ get_tables_to_cluster(MemoryContext cluster_context)
return rvs;
}
+
+/*
+ * For global temporary table, storage information is stored in localhash,
+ * This function like swap_relation_files except that update storage information,
+ * in the localhash, not pg_class.
+ */
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+ bool swap_toast_by_content,
+ bool is_internal,
+ TransactionId frozenXid,
+ MultiXactId cutoffMulti,
+ Oid *mapped_tables)
+{
+ Relation relRelation;
+ Oid relfilenode1,
+ relfilenode2;
+ Relation rel1;
+ Relation rel2;
+
+ relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+ rel1 = relation_open(r1, AccessExclusiveLock);
+ rel2 = relation_open(r2, AccessExclusiveLock);
+
+ relfilenode1 = gtt_fetch_current_relfilenode(r1);
+ relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+ Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+ gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+ CacheInvalidateRelcache(rel1);
+ CacheInvalidateRelcache(rel2);
+
+ InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+ InvalidOid, is_internal);
+ InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+ InvalidOid, true);
+
+ if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+ {
+ if (swap_toast_by_content)
+ {
+ if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+ {
+ gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+ rel2->rd_rel->reltoastrelid,
+ target_is_pg_class,
+ swap_toast_by_content,
+ is_internal,
+ frozenXid,
+ cutoffMulti,
+ mapped_tables);
+ }
+ else
+ elog(ERROR, "cannot swap toast files by content when there's only one");
+ }
+ else
+ {
+ ObjectAddress baseobject,
+ toastobject;
+ long count;
+
+ if (IsSystemRelation(rel1))
+ elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+ if (rel1->rd_rel->reltoastrelid)
+ {
+ count = deleteDependencyRecordsFor(RelationRelationId,
+ rel1->rd_rel->reltoastrelid,
+ false);
+ if (count != 1)
+ elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+ count);
+ }
+ if (rel2->rd_rel->reltoastrelid)
+ {
+ count = deleteDependencyRecordsFor(RelationRelationId,
+ rel2->rd_rel->reltoastrelid,
+ false);
+ if (count != 1)
+ elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+ count);
+ }
+
+ /* Register new dependencies */
+ baseobject.classId = RelationRelationId;
+ baseobject.objectSubId = 0;
+ toastobject.classId = RelationRelationId;
+ toastobject.objectSubId = 0;
+
+ if (rel1->rd_rel->reltoastrelid)
+ {
+ baseobject.objectId = r1;
+ toastobject.objectId = rel1->rd_rel->reltoastrelid;
+ recordDependencyOn(&toastobject, &baseobject,
+ DEPENDENCY_INTERNAL);
+ }
+
+ if (rel2->rd_rel->reltoastrelid)
+ {
+ baseobject.objectId = r2;
+ toastobject.objectId = rel2->rd_rel->reltoastrelid;
+ recordDependencyOn(&toastobject, &baseobject,
+ DEPENDENCY_INTERNAL);
+ }
+ }
+ }
+
+ if (swap_toast_by_content &&
+ rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+ rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+ {
+ Oid toastIndex1,
+ toastIndex2;
+
+ /* Get valid index for each relation */
+ toastIndex1 = toast_get_valid_index(r1,
+ AccessExclusiveLock);
+ toastIndex2 = toast_get_valid_index(r2,
+ AccessExclusiveLock);
+
+ gtt_swap_relation_files(toastIndex1,
+ toastIndex2,
+ target_is_pg_class,
+ swap_toast_by_content,
+ is_internal,
+ InvalidTransactionId,
+ InvalidMultiXactId,
+ mapped_tables);
+ }
+
+ relation_close(rel1, NoLock);
+ relation_close(rel2, NoLock);
+
+ table_close(relRelation, RowExclusiveLock);
+
+ RelationCloseSmgrByOid(r1);
+ RelationCloseSmgrByOid(r2);
+
+ CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index b6143b8..e8da86c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -290,7 +290,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
Assert(rel);
/* check read-only transaction and parallel mode */
- if (XactReadOnly && !rel->rd_islocaltemp)
+ if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
PreventCommandIfReadOnly("COPY FROM");
cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 1b14e9a..5e918f2 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -23,6 +23,7 @@
#include "access/tableam.h"
#include "access/xact.h"
#include "access/xlog.h"
+#include "catalog/storage_gtt.h"
#include "commands/copy.h"
#include "commands/copyfrom_internal.h"
#include "commands/trigger.h"
@@ -655,6 +656,9 @@ CopyFrom(CopyFromState cstate)
ExecOpenIndices(resultRelInfo, false);
+ /* Check and init global temporary table storage in current backend */
+ init_gtt_storage(CMD_INSERT, resultRelInfo);
+
/*
* Set up a ModifyTableState so we can let FDW(s) init themselves for
* foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 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;
+
I found that the new Patch mail failed to register to Commitfest
https://commitfest.postgresql.org/28/2349/# <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?
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 loand 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 INDEXclose session and in new session
postgres=# reindex index foo_a_idx ;
WARNING: relcache reference leak: relation "foo" not closed
REINDEXRegards
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/>
The Enterprise PostgreSQL Company
Attachments:
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.
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/
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 loand 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 INDEXclose session and in new session
postgres=# reindex index foo_a_idx ;
WARNING: relcache reference leak: relation "foo" not closed
REINDEXI 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
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(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (!found)
+ {
+ int wordnum;
+
+ /* init bitmap */
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ /* record itself in bitmap */
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ /* remove itself from bitmap */
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+
+ /* copy the entire bitmap */
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backand.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ /* how many backend are using this GTT */
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ /* check if this is itself */
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+ int natts = 0;
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ /* First time through: initialize the hash table */
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ HASHCTL ctl;
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_info_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt info context",
+ ALLOCSET_DEFAULT_SIZES);
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ ctl.hcxt = gtt_info_context;
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+ }
+
+ Assert(CacheMemoryContext);
+ Assert(gtt_info_context);
+ oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->oldrelid = InvalidOid;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ /* record the on commit clause */
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ /* record storage info relstat columnstats and transaction info to relfilenode list */
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ new_node->natts = 0;
+ new_node->attnum = NULL;
+ new_node->att_stat_tups = NULL;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* init column stats structure */
+ natts = RelationGetNumberOfAttributes(rel);
+ new_node->attnum = palloc0(sizeof(int) * natts);
+ new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ new_node->natts = natts;
+
+ /* only heap have transaction info */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+
+ /**/
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+
+ /* Registration callbacks are used to trigger cleanup during process exit */
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ /*
+ * For cluster GTT rollback.
+ * We need to roll back the exchange relfilenode operation.
+ */
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+
+ /* temp relfilenode need free */
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ /* rollback transaction */
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ /* Clean up transaction info from Local order list and MyProc */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+ /* this is valid relfrozenxid */
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ /* delete relfilenode from rel entry */
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ gtt_free_statistics(d_rnode);
+
+ if (entry->relfilenode_list == NIL)
+ {
+ /* this means we truncate this GTT at current backend */
+
+ /* tell shared hash that current backend will no longer use this GTT */
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ /* commit transaction at cluster GTT, need clean up footprint */
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ entry2->oldrelid = InvalidOid;
+ }
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (!OidIsValid(relid))
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ /* Search all relfilenode for GTT in current backend */
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ /* drop local buffer and storage */
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ /* tell shared hash */
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ /* set to global area */
+ MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!OidIsValid(relid))
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return;
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return;
+
+ if (num_pages > 0 &&
+ gtt_rnode->relpages != (int32)num_pages)
+ gtt_rnode->relpages = (int32)num_pages;
+
+ if (num_tuples > 0 &&
+ gtt_rnode->reltuples != (float4)num_tuples)
+ gtt_rnode->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (num_all_visible_pages > 0 &&
+ gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+ {
+ gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ gtt_rnode->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(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,
+ >t_relfrozenxid, >t_relminmxid);
+
+ if (TransactionIdIsValid(gtt_relfrozenxid) &&
+ TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+ FreezeXid = gtt_relfrozenxid;
+
+ if (MultiXactIdIsValid(gtt_relminmxid) &&
+ MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+ MultiXactCutoff = gtt_relminmxid;
+ }
+ else
+ {
+ /*
+ * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+ * backwards, so take the max.
+ */
+ if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+ TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+ FreezeXid = OldHeap->rd_rel->relfrozenxid;
- /*
- * MultiXactCutoff, similarly, shouldn't go backwards either.
- */
- if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
- MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
- MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+ /*
+ * MultiXactCutoff, similarly, shouldn't go backwards either.
+ */
+ if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+ MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+ MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+ }
/*
* Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -935,6 +978,15 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
table_close(OldHeap, NoLock);
table_close(NewHeap, NoLock);
+ /* Update relstats of global temporary table to localhash. */
+ if (is_gtt)
+ {
+ up_gtt_relstats(RelationGetRelid(NewHeap), num_pages, num_tuples, 0,
+ InvalidTransactionId, InvalidMultiXactId);
+ CommandCounterIncrement();
+ return;
+ }
+
/* Update pg_class to reflect the correct values of pages and tuples. */
relRelation = table_open(RelationRelationId, RowExclusiveLock);
@@ -1371,10 +1423,22 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
* Swap the contents of the heap relations (including any toast tables).
* Also set old heap's relfrozenxid to frozenXid.
*/
- swap_relation_files(OIDOldHeap, OIDNewHeap,
+ if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ Assert(!is_system_catalog);
+ /* For global temporary table modify data in localhash, not pg_class */
+ gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+ (OIDOldHeap == RelationRelationId),
+ swap_toast_by_content, is_internal,
+ frozenXid, cutoffMulti, mapped_tables);
+ }
+ else
+ {
+ swap_relation_files(OIDOldHeap, OIDNewHeap,
(OIDOldHeap == RelationRelationId),
swap_toast_by_content, is_internal,
frozenXid, cutoffMulti, mapped_tables);
+ }
/*
* If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1582,3 +1646,146 @@ get_tables_to_cluster(MemoryContext cluster_context)
return rvs;
}
+
+/*
+ * For global temporary table, storage information is stored in localhash,
+ * This function like swap_relation_files except that update storage information,
+ * in the localhash, not pg_class.
+ */
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+ bool swap_toast_by_content,
+ bool is_internal,
+ TransactionId frozenXid,
+ MultiXactId cutoffMulti,
+ Oid *mapped_tables)
+{
+ Relation relRelation;
+ Oid relfilenode1,
+ relfilenode2;
+ Relation rel1;
+ Relation rel2;
+
+ relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+ rel1 = relation_open(r1, AccessExclusiveLock);
+ rel2 = relation_open(r2, AccessExclusiveLock);
+
+ relfilenode1 = gtt_fetch_current_relfilenode(r1);
+ relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+ Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+ gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+ CacheInvalidateRelcache(rel1);
+ CacheInvalidateRelcache(rel2);
+
+ InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+ InvalidOid, is_internal);
+ InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+ InvalidOid, true);
+
+ if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+ {
+ if (swap_toast_by_content)
+ {
+ if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+ {
+ gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+ rel2->rd_rel->reltoastrelid,
+ target_is_pg_class,
+ swap_toast_by_content,
+ is_internal,
+ frozenXid,
+ cutoffMulti,
+ mapped_tables);
+ }
+ else
+ elog(ERROR, "cannot swap toast files by content when there's only one");
+ }
+ else
+ {
+ ObjectAddress baseobject,
+ toastobject;
+ long count;
+
+ if (IsSystemRelation(rel1))
+ elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+ if (rel1->rd_rel->reltoastrelid)
+ {
+ count = deleteDependencyRecordsFor(RelationRelationId,
+ rel1->rd_rel->reltoastrelid,
+ false);
+ if (count != 1)
+ elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+ count);
+ }
+ if (rel2->rd_rel->reltoastrelid)
+ {
+ count = deleteDependencyRecordsFor(RelationRelationId,
+ rel2->rd_rel->reltoastrelid,
+ false);
+ if (count != 1)
+ elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+ count);
+ }
+
+ /* Register new dependencies */
+ baseobject.classId = RelationRelationId;
+ baseobject.objectSubId = 0;
+ toastobject.classId = RelationRelationId;
+ toastobject.objectSubId = 0;
+
+ if (rel1->rd_rel->reltoastrelid)
+ {
+ baseobject.objectId = r1;
+ toastobject.objectId = rel1->rd_rel->reltoastrelid;
+ recordDependencyOn(&toastobject, &baseobject,
+ DEPENDENCY_INTERNAL);
+ }
+
+ if (rel2->rd_rel->reltoastrelid)
+ {
+ baseobject.objectId = r2;
+ toastobject.objectId = rel2->rd_rel->reltoastrelid;
+ recordDependencyOn(&toastobject, &baseobject,
+ DEPENDENCY_INTERNAL);
+ }
+ }
+ }
+
+ if (swap_toast_by_content &&
+ rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+ rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+ {
+ Oid toastIndex1,
+ toastIndex2;
+
+ /* Get valid index for each relation */
+ toastIndex1 = toast_get_valid_index(r1,
+ AccessExclusiveLock);
+ toastIndex2 = toast_get_valid_index(r2,
+ AccessExclusiveLock);
+
+ gtt_swap_relation_files(toastIndex1,
+ toastIndex2,
+ target_is_pg_class,
+ swap_toast_by_content,
+ is_internal,
+ InvalidTransactionId,
+ InvalidMultiXactId,
+ mapped_tables);
+ }
+
+ relation_close(rel1, NoLock);
+ relation_close(rel2, NoLock);
+
+ table_close(relRelation, RowExclusiveLock);
+
+ RelationCloseSmgrByOid(r1);
+ RelationCloseSmgrByOid(r2);
+
+ CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 8c712c8..0a35f6f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -290,7 +290,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
Assert(rel);
/* check read-only transaction and parallel mode */
- if (XactReadOnly && !rel->rd_islocaltemp)
+ if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
PreventCommandIfReadOnly("COPY FROM");
cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 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;
+
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
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
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�L T�C~��q�q7���<�+;WF� �tCB�Z^���bO�G���v�eJ0N�b������G�� ��'��a4W�e���3��P��(�j��Ot�\,fH@b BH�!�}�|{��M�0�A�]��F�<