pg.ddx.io pgsql-hackers@postgresql.org mailing list archive
help / color / mirror / Atom feedglobal temporary tables
525+ messages / 47 participants
[nested] [flat]
* global temporary tables
@ 2010-04-24 02:52 Robert Haas <robertmhaas@gmail.com>
0 siblings, 4 replies; 525+ messages in thread
From: Robert Haas @ 2010-04-24 02:52 UTC (permalink / raw)
To: pgsql-hackers
A couple of recent threads made got me thinking again about the idea
of global temporary tables. There seem to be two principal issues:
1. What is a global temporary table?
2. How could we implement that?
Despite rereading the "idea: global temp tables" thread from April
2009 in some detail, I was not able to get a clear understanding of
(1). What I *think* it is supposed to mean is that the table is a
permanent object which is "globally" visible - that is, it's part of
some non-temp schema like public or $user and it's column definitions
etc. are visible to all backends - and it's not automatically removed
on commit, backend exit, etc. - but the *contents* of the table are
temporary and backend-local, so that each new backend initially sees
it as empty and can then insert, update, and delete data independently
of what any other backend does.
As to (2), my thought is that perhaps we could implement this by
instantiating a separate relfilenode for the relation for each backend
which accesses it. relfilenode would be 0 in pg_class, as it is for
"mapped" relations, but every time a backend touched the rel, we'd
allocate a relfilenode and associated the oid of the temp table to it
using some kind of backend-local storage - actually similar to what
the relmapper code does, except without the complexity of ever
actually having to persist the value; and perhaps using a hash table
rather than an array, since the number of mapped rels that a backend
can need to deal with is rather more limited than the number of temp
tables it might want to use.
Thoughts?
...Robert
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-24 03:11 Tom Lane <tgl@sss.pgh.pa.us>
parent: Robert Haas <robertmhaas@gmail.com>
3 siblings, 1 reply; 525+ messages in thread
From: Tom Lane @ 2010-04-24 03:11 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
> A couple of recent threads made got me thinking again about the idea
> of global temporary tables. There seem to be two principal issues:
> 1. What is a global temporary table?
> 2. How could we implement that?
> Despite rereading the "idea: global temp tables" thread from April
> 2009 in some detail, I was not able to get a clear understanding of
> (1).
I believe that the spec's distinction between global and local temp
tables has to do with whether they are visible across module
boundaries. Since we haven't implemented modules, that distinction
is meaningless to us. In the spec, *both* types of temp tables have
the property that the definition (schema) of the table is global
across all sessions, and only the content of the table is session-local.
This arrangement clearly is useful for some applications, but so is our
current definition wherein different sessions can have different schemas
for the same temp table name. So eventually it'd be good to support
both. But the GLOBAL/LOCAL TEMP TABLE distinction is something entirely
different. PG's behavior does not correspond to either of those.
Your idea of using the relmapper layer to instantiate copies of temp
tables is an interesting one. It's only a small piece of the puzzle
though. In particular, what you described would result in the table
having the same OID in all sessions, even though the relfilenodes are
different --- amd since locking is done on the basis of OID, that's
probably *not* what we want. It would be much better for performance
if the different sessions' versions of the table were independently
lockable.
I also kind of wonder what is supposed to happen if someone DROPs or
ALTERs the temp table definition ...
regards, tom lane
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-24 03:18 Robert Haas <robertmhaas@gmail.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 525+ messages in thread
From: Robert Haas @ 2010-04-24 03:18 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: pgsql-hackers
On Fri, Apr 23, 2010 at 11:11 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
>> A couple of recent threads made got me thinking again about the idea
>> of global temporary tables. There seem to be two principal issues:
>
>> 1. What is a global temporary table?
>
>> 2. How could we implement that?
>
>> Despite rereading the "idea: global temp tables" thread from April
>> 2009 in some detail, I was not able to get a clear understanding of
>> (1).
>
> I believe that the spec's distinction between global and local temp
> tables has to do with whether they are visible across module
> boundaries. Since we haven't implemented modules, that distinction
> is meaningless to us. In the spec, *both* types of temp tables have
> the property that the definition (schema) of the table is global
> across all sessions, and only the content of the table is session-local.
>
> This arrangement clearly is useful for some applications, but so is our
> current definition wherein different sessions can have different schemas
> for the same temp table name. So eventually it'd be good to support
> both. But the GLOBAL/LOCAL TEMP TABLE distinction is something entirely
> different. PG's behavior does not correspond to either of those.
I don't really care what we call it, although I find the GLOBAL name
convenient and descriptive.
> Your idea of using the relmapper layer to instantiate copies of temp
> tables is an interesting one. It's only a small piece of the puzzle
> though. In particular, what you described would result in the table
> having the same OID in all sessions, even though the relfilenodes are
> different --- amd since locking is done on the basis of OID, that's
> probably *not* what we want. It would be much better for performance
> if the different sessions' versions of the table were independently
> lockable.
Well, it depends on what operation we're talking about. For
operations that involve only the table contents, yeah, we'd like to
lock the versions independently. But for this sort of thing:
> I also kind of wonder what is supposed to happen if someone DROPs or
> ALTERs the temp table definition ...
...not so much. Here you REALLY want a DROP attempt to acquire an
AccessExclusiveLock that will conflict with any outstanding
AccessShareLocks. Similarly, you're only going to be able to modify
the schema for the relation if it's not otherwise in use.
...Robert
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-24 03:28 Tom Lane <tgl@sss.pgh.pa.us>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Tom Lane @ 2010-04-24 03:28 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
> On Fri, Apr 23, 2010 at 11:11 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> I also kind of wonder what is supposed to happen if someone DROPs or
>> ALTERs the temp table definition ...
> ...not so much. Here you REALLY want a DROP attempt to acquire an
> AccessExclusiveLock that will conflict with any outstanding
> AccessShareLocks. Similarly, you're only going to be able to modify
> the schema for the relation if it's not otherwise in use.
I think you're presuming the answer to the question. We could also view
the desired behavior as being that each session clones the temp table
definition at some instant (eg, first use). The approach that you're
assuming seems fraught with large downsides: in particular, implementing
ALTER TABLE would be a mess. The would-be alterer would need access to
the physical copies of all sessions, which throws out not only the
assumption that the relmapper entries can be private data, but all of
the access optimizations we currently have in the local buffer manager.
Not to mention the coding mess of having to repeat the ALTER operation
for each of N copies, some of which might disappear while we're trying
to do it (or if they don't, we're blocking backends from exiting).
I don't even know how you'd do the ALTER over again N times if you
only have one set of catalog entries describing the N copies.
regards, tom lane
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-24 03:46 Robert Haas <robertmhaas@gmail.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 525+ messages in thread
From: Robert Haas @ 2010-04-24 03:46 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: pgsql-hackers
On Fri, Apr 23, 2010 at 11:28 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
>> On Fri, Apr 23, 2010 at 11:11 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>>> I also kind of wonder what is supposed to happen if someone DROPs or
>>> ALTERs the temp table definition ...
>
>> ...not so much. Here you REALLY want a DROP attempt to acquire an
>> AccessExclusiveLock that will conflict with any outstanding
>> AccessShareLocks. Similarly, you're only going to be able to modify
>> the schema for the relation if it's not otherwise in use.
>
> I think you're presuming the answer to the question. We could also view
> the desired behavior as being that each session clones the temp table
> definition at some instant (eg, first use). The approach that you're
> assuming seems fraught with large downsides: in particular, implementing
> ALTER TABLE would be a mess. The would-be alterer would need access to
> the physical copies of all sessions, which throws out not only the
> assumption that the relmapper entries can be private data, but all of
> the access optimizations we currently have in the local buffer manager.
I agree, that would be pretty unfortunate, althogh maybe it's the only
way to make it work. It's not what I had in mind. I was thinking
that the would-be ALTERER could just take an AccessExclusiveLock, but
now that I think about it that doesn't work, since a backend could
have the table unlocked between transactions but still have private
contents in it. :-(
> Not to mention the coding mess of having to repeat the ALTER operation
> for each of N copies, some of which might disappear while we're trying
> to do it (or if they don't, we're blocking backends from exiting).
> I don't even know how you'd do the ALTER over again N times if you
> only have one set of catalog entries describing the N copies.
Well, if you clone the table, that just pushes the problem around.
When I run ALTER TABLE on one of these thingamabobs, does it modify my
clone? The original? Both? If it modifies my clone, how do we
modify the original? If it modifies the original, won't I be rather
surprised to find my clone unaffected? If it modifies both, how do we
avoid complete havoc if the original has since been modified (perhaps
incompatibly, perhaps not) by some other backend doing its own ALTER
TABLE?
...Robert
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-24 06:42 Pavel Stehule <pavel.stehule@gmail.com>
parent: Robert Haas <robertmhaas@gmail.com>
3 siblings, 0 replies; 525+ messages in thread
From: Pavel Stehule @ 2010-04-24 06:42 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: pgsql-hackers
2010/4/24 Robert Haas <robertmhaas@gmail.com>:
> A couple of recent threads made got me thinking again about the idea
> of global temporary tables. There seem to be two principal issues:
>
> 1. What is a global temporary table?
>
> 2. How could we implement that?
>
> Despite rereading the "idea: global temp tables" thread from April
> 2009 in some detail, I was not able to get a clear understanding of
> (1). What I *think* it is supposed to mean is that the table is a
> permanent object which is "globally" visible - that is, it's part of
> some non-temp schema like public or $user and it's column definitions
> etc. are visible to all backends - and it's not automatically removed
> on commit, backend exit, etc. - but the *contents* of the table are
> temporary and backend-local, so that each new backend initially sees
> it as empty and can then insert, update, and delete data independently
> of what any other backend does.
>
> As to (2), my thought is that perhaps we could implement this by
> instantiating a separate relfilenode for the relation for each backend
> which accesses it. relfilenode would be 0 in pg_class, as it is for
> "mapped" relations, but every time a backend touched the rel, we'd
> allocate a relfilenode and associated the oid of the temp table to it
> using some kind of backend-local storage - actually similar to what
> the relmapper code does, except without the complexity of ever
> actually having to persist the value; and perhaps using a hash table
> rather than an array, since the number of mapped rels that a backend
> can need to deal with is rather more limited than the number of temp
> tables it might want to use.
it is good idea.
I missing some ideas about statistics, about indexes.
Regards
Pavel Stehule
>
> Thoughts?
>
> ...Robert
>
> --
> Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-hackers
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-24 16:02 Greg Sabino Mullane <greg@turnstep.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Greg Sabino Mullane @ 2010-04-24 16:02 UTC (permalink / raw)
To: pgsql-hackers
-----BEGIN PGP SIGNED MESSAGE-----
Hash: RIPEMD160
>...
> surprised to find my clone unaffected? If it modifies both, how do we
> avoid complete havoc if the original has since been modified (perhaps
> incompatibly, perhaps not) by some other backend doing its own ALTER
> TABLE?
Since this is such a thorny problem, and this is a temporary table, why
not just disallow ALTER completely for the first pass?
- --
Greg Sabino Mullane greg@turnstep.com
PGP Key: 0x14964AC8 201004241201
http://biglumber.com/x/web?pk=2529DF6AB8F79407E94445B4BC9B906714964AC8
-----BEGIN PGP SIGNATURE-----
iEYEAREDAAYFAkvTFfsACgkQvJuQZxSWSsjrVACePmmNglGi6KoZgYZ7zjUm4gPm
o2wAoNYYuiZl1HZXsgiAOQkJzNUmaORm
=IijV
-----END PGP SIGNATURE-----
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-24 16:11 Tom Lane <tgl@sss.pgh.pa.us>
parent: Greg Sabino Mullane <greg@turnstep.com>
0 siblings, 1 reply; 525+ messages in thread
From: Tom Lane @ 2010-04-24 16:11 UTC (permalink / raw)
To: Greg Sabino Mullane <greg@turnstep.com>; +Cc: pgsql-hackers
"Greg Sabino Mullane" <greg@turnstep.com> writes:
>> surprised to find my clone unaffected? If it modifies both, how do we
>> avoid complete havoc if the original has since been modified (perhaps
>> incompatibly, perhaps not) by some other backend doing its own ALTER
>> TABLE?
> Since this is such a thorny problem, and this is a temporary table, why
> not just disallow ALTER completely for the first pass?
Usually the way we approach these kinds of problems is that we want
to see some plausible outline for how they might be fixed before we
move forward with the base feature. IOW, I wouldn't object to not
having ALTER in the first release, but if we have no idea how to do
ALTER at all I'd be too worried that we were painting ourselves into
a corner.
Or maybe you can make a case that there's no need to allow ALTER at
all, ever. But surely DROP needs to be possible, and that seems to
already introduce some of the same issues.
regards, tom lane
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-24 17:16 Robert Haas <robertmhaas@gmail.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 2 replies; 525+ messages in thread
From: Robert Haas @ 2010-04-24 17:16 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
On Sat, Apr 24, 2010 at 12:11 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> "Greg Sabino Mullane" <greg@turnstep.com> writes:
>>> surprised to find my clone unaffected? If it modifies both, how do we
>>> avoid complete havoc if the original has since been modified (perhaps
>>> incompatibly, perhaps not) by some other backend doing its own ALTER
>>> TABLE?
>
>> Since this is such a thorny problem, and this is a temporary table, why
>> not just disallow ALTER completely for the first pass?
>
> Usually the way we approach these kinds of problems is that we want
> to see some plausible outline for how they might be fixed before we
> move forward with the base feature. IOW, I wouldn't object to not
> having ALTER in the first release, but if we have no idea how to do
> ALTER at all I'd be too worried that we were painting ourselves into
> a corner.
>
> Or maybe you can make a case that there's no need to allow ALTER at
> all, ever. But surely DROP needs to be possible, and that seems to
> already introduce some of the same issues.
I had the same thought as GSM this morning. More specifically, it
seems to me that the problematic cases are precisely those in which
you might feel an urge to touch somebody else's local buffers, so I
think we should disallow, approximately, those ALTER TABLE cases which
require a full-table rewrite. I don't see the problem with DROP.
Under the proposed design, it's approximately equivalent to dropping a
table that someone else has truncated. You just wait for the
necessary lock and then do it.
At least AIUI, the use case for this feature is that you want to avoid
creating "the same" temporary table over and over again. The schema
is fixed and doesn't change much, but you're creating it lots and lots
of times in lots and lots of different backends, leading to both
management and performance difficulties. If you want to be able to
change the schema frequently or in a backend-local way, use the
existing temporary table feature.
Now, there is ONE problem with DROP, which is that you might orphan
some heaps. Of course, that can also happen due to a backend crash.
Currently, autovacuum arranges to drop any orphaned temp tables that
have passed the wraparound threshold, but even if we were happy with
waiting 2 billion transactions to get things cleaned up, the mechanism
can't work here because it relies on being able to examine the
pg_class row and determine which backend owns it, and where the
storage is located.
We could possibly set things up so that a running backend will notice
if a global temporary table for which it's created a private
relfilenode gets dropped, and blow away the backing file. But that
doesn't protect against crashes, so I think we're going to need some
other garbage collection mechanism, either instead of in addition to
asking backends to clean up after themselves. I'm not quite sure what
the design of that should look like, though.
...Robert
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-24 17:31 Tom Lane <tgl@sss.pgh.pa.us>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 2 replies; 525+ messages in thread
From: Tom Lane @ 2010-04-24 17:31 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
> At least AIUI, the use case for this feature is that you want to avoid
> creating "the same" temporary table over and over again.
The context that I've seen it come up in is that people don't want to
clutter their functions with create-it-if-it-doesn't-exist logic,
which you have to have given the current behavior of temp tables.
Any performance gain from reduced catalog churn would be gravy.
Aside from the DROP problem, I think this implementation proposal
has one other big shortcoming: what are you going to do about
table statistics? In many cases, you really *have* to do an ANALYZE
once you've populated a temp table, if you want to get decent plans
for it. Where will you put those stats?
regards, tom lane
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-24 17:38 Tom Lane <tgl@sss.pgh.pa.us>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 2 replies; 525+ messages in thread
From: Tom Lane @ 2010-04-24 17:38 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
[ forgot to respond to this part ]
Robert Haas <robertmhaas@gmail.com> writes:
> ... I don't see the problem with DROP.
> Under the proposed design, it's approximately equivalent to dropping a
> table that someone else has truncated. You just wait for the
> necessary lock and then do it.
And do *what*? You can remove the catalog entries, but how are you
going to make the physical storage of other backends' versions go away?
(To say nothing of making them flush their local buffers for it.)
If you do remove the catalog entries, won't you be cutting the knees
out from under whatever end-of-session cleanup processing might exist
in those other backends?
The idea of the global table as a template that individual sessions
clone working tables from would avoid most of these problems. You
rejected it on the grounds that ALTER would be too hard; but if you're
blowing off ALTER anyway, that argument seems pretty unimpressive.
regards, tom lane
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-24 18:32 Robert Haas <robertmhaas@gmail.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 1 reply; 525+ messages in thread
From: Robert Haas @ 2010-04-24 18:32 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
On Sat, Apr 24, 2010 at 1:31 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
>> At least AIUI, the use case for this feature is that you want to avoid
>> creating "the same" temporary table over and over again.
>
> The context that I've seen it come up in is that people don't want to
> clutter their functions with create-it-if-it-doesn't-exist logic,
> which you have to have given the current behavior of temp tables.
> Any performance gain from reduced catalog churn would be gravy.
I think there's a significant contingent on this mailing list who feel
that that gravy would be rather tasty and would like very much to
enjoy some of it along with their temporary table tetrazzini.
> Aside from the DROP problem, I think this implementation proposal
> has one other big shortcoming: what are you going to do about
> table statistics? In many cases, you really *have* to do an ANALYZE
> once you've populated a temp table, if you want to get decent plans
> for it. Where will you put those stats?
For a first cut, I had thought about ignoring the problem. Now, that
may sound stupid, because now if two different backends have very
different distributions of data in the table and both do an ANALYZE,
one set of statistics will clobber the other set of statistics. On
the flip side, for some usage patterns, it might be actually work out
to a win. Maybe the data I'm putting in here today is a great deal
like the data I put in here yesterday, and planning it with
yesterday's statistics doesn't cost enough to be worth a re-ANALYZE.
If we don't want to do that, I suppose one option is to create a
pg_statistic-like table in the backend's temporary tablespace and put
them there; or we could put them into a backend-local hash table. The
current setup of pg_statistic is actually somewhat weak for a number
of things we might want to do: for example, it might be interesting to
gather statistics for the subset of a table for which a particular
partial index is predOK. When such an index is available for a
particular query, we could use the statistics for that subset of the
table instead of the overall statistics for the table, and get better
estimates. Or we could even let the user specify predicates which
will cause the table to have a different statistical distribution than
the table as a whole, and gather statistics for the subset that
matches the predicate. One approach would be to make the starelid
column able to reference something other than a relation OID, although
I don't think that actually helps with the global temp table problem
because if we use the real pg_statistic to store the data then we have
to arrange to clean it up.
...Robert
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-24 18:41 Pavel Stehule <pavel.stehule@gmail.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 0 replies; 525+ messages in thread
From: Pavel Stehule @ 2010-04-24 18:41 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
>
> For a first cut, I had thought about ignoring the problem. Now, that
> may sound stupid, because now if two different backends have very
> different distributions of data in the table and both do an ANALYZE,
> one set of statistics will clobber the other set of statistics. On
> the flip side, for some usage patterns, it might be actually work out
> to a win. Maybe the data I'm putting in here today is a great deal
> like the data I put in here yesterday, and planning it with
> yesterday's statistics doesn't cost enough to be worth a re-ANALYZE.
>
Both variant can be. First time - statistic can be taken from some
"original" (can be empty). After ANALYZE the statistic can be
individual.
Regards
Pavel
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-24 18:51 Robert Haas <robertmhaas@gmail.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 1 reply; 525+ messages in thread
From: Robert Haas @ 2010-04-24 18:51 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
On Sat, Apr 24, 2010 at 1:38 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> [ forgot to respond to this part ]
>
> Robert Haas <robertmhaas@gmail.com> writes:
>> ... I don't see the problem with DROP.
>> Under the proposed design, it's approximately equivalent to dropping a
>> table that someone else has truncated. You just wait for the
>> necessary lock and then do it.
>
> And do *what*? You can remove the catalog entries, but how are you
> going to make the physical storage of other backends' versions go away?
> (To say nothing of making them flush their local buffers for it.)
> If you do remove the catalog entries, won't you be cutting the knees
> out from under whatever end-of-session cleanup processing might exist
> in those other backends?
Well, if I knew for sure what the best way was to solve all of these
problems, I'd be posting a finished patch rather than a request for
comment on a design. It's not obvious to me that there's a terribly
thorny problem in the area you're concerned about, but your concern is
making me worry that I'm missing something. Why would the
end-of-session processing need the catalog entries? It seems like
whatever backend-local data structure we're using to record the
relfilenode mappings would be sufficent to nuke the backend storage,
and what else needs doing?
> The idea of the global table as a template that individual sessions
> clone working tables from would avoid most of these problems. You
> rejected it on the grounds that ALTER would be too hard; but if you're
> blowing off ALTER anyway, that argument seems pretty unimpressive.
I don't think that avoiding the catalog churn is something to be
dismissed lightly, but I also think that cloning the table is likely
to be significantly more difficult from an implementation point of
view. Under the implementation I'm proposing, we don't need much that
is fundamentally all that new. Global temporary tables can be treated
like our existing temp tables for purposes of XLog and bufmgr, but
they don't get forced into a temporary namespace. The relation
mapping infrastructure provides a pretty good start for using a
relfilenode that isn't stored in pg_class. I've already gone through
the exercise of finding all the places where we check rd_istemp and
changing them to use macros instead (RelationNeedsWAL, IsBackendLocal,
etc.) and it's not bad.
There's a related project which I think can also leverage much of this
same infrastructure: unlogged tables. We've talked about this before,
but in short the idea is that an unlogged table behaves like a regular
table in all respects except that we never write WAL for it; and we
truncate it at shutdown and at startup. Therefore, it doesn't show up
on standby servers, and its contents are not persistent across
restarts, but performance is improved. It's suitable for things like
"the table of currently logged in users", where you don't mind forcing
everyone to log in again if the database crashes. (It might even be
possible to allow writes to unlogged tables on standby servers, though
I'm not feeling that ambitious ATM.) So:
- local temp tables exist in a temp namespace, use local buffers, and skip WAL
- global temp tables exist in a non-temp namespace, use local buffers,
and skip WAL
- unlogged tables exist in a non-temp namespace, use shared buffers,
and skip WAL
- normal tables exist in a non-temp namespace, use shared buffers, and write WAL
Thoughts?
...Robert
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-24 19:51 Tom Lane <tgl@sss.pgh.pa.us>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Tom Lane @ 2010-04-24 19:51 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
> ... Why would the
> end-of-session processing need the catalog entries? It seems like
> whatever backend-local data structure we're using to record the
> relfilenode mappings would be sufficent to nuke the backend storage,
> and what else needs doing?
Well, if you're intending to have a separate data structure and code
path for cleaning up this type of temp table, then maybe you don't need
to touch any catalog entries. I'm concerned though about how far the
effects will propagate --- things like TRUNCATE, VACUUM FULL, REINDEX
will probably have issues with this. Right now they think in terms
of writing a new pg_class entry in order to reassociate tables with
new relfilenodes.
Have you thought much about the previously proposed design, ie keeping
catalog entries for temp tables in backend-local temporary catalogs?
That would certainly be a lot of work, but I think in the end it might
fit in better. This design feels like it's being driven by "hey,
we can abuse the relmapper to sort of do what we want", and not by
what we really want.
regards, tom lane
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-24 22:29 Simon Riggs <simon@2ndQuadrant.com>
parent: Robert Haas <robertmhaas@gmail.com>
3 siblings, 1 reply; 525+ messages in thread
From: Simon Riggs @ 2010-04-24 22:29 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: pgsql-hackers
On Fri, 2010-04-23 at 22:52 -0400, Robert Haas wrote:
> Thoughts?
Only a requirement: that we design this in a way that will allow temp
tables to be used during Hot Standby. I make not other comment.
--
Simon Riggs www.2ndQuadrant.com
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-24 22:53 Robert Haas <robertmhaas@gmail.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 0 replies; 525+ messages in thread
From: Robert Haas @ 2010-04-24 22:53 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
On Sat, Apr 24, 2010 at 3:51 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
>> ... Why would the
>> end-of-session processing need the catalog entries? It seems like
>> whatever backend-local data structure we're using to record the
>> relfilenode mappings would be sufficent to nuke the backend storage,
>> and what else needs doing?
>
> Well, if you're intending to have a separate data structure and code
> path for cleaning up this type of temp table, then maybe you don't need
> to touch any catalog entries. I'm concerned though about how far the
> effects will propagate --- things like TRUNCATE, VACUUM FULL, REINDEX
> will probably have issues with this. Right now they think in terms
> of writing a new pg_class entry in order to reassociate tables with
> new relfilenodes.
>
> Have you thought much about the previously proposed design, ie keeping
> catalog entries for temp tables in backend-local temporary catalogs?
> That would certainly be a lot of work, but I think in the end it might
> fit in better. This design feels like it's being driven by "hey,
> we can abuse the relmapper to sort of do what we want", and not by
> what we really want.
Well, yes and no. I think there are definitely some good things that
can happen if we can see our way to taking a hammer to pg_class and
pg_attribute. If we create, e.g. pg_shared_class and
pg_shared_attribute, then we can un-nail the catalogs you just nailed
to make the authentication process able to work without selecting a
database. We can also enable (without guilt) clustering both those
catalogs and the database-specific versions of pg_class, since we no
longer have to worry about having multiple copies of the row that can
get out of sync with each other. And if we further break off
pg_temp_class and pg_temp_attribute, then we can also have our
existing flavor of temporary tables without worrying about catalog
bloat, which would be great. There may be other applications as well.
Having said all that, it doesn't actually allow us to implement global
temporary tables, because obviously the catalog entries for a global
temporary table have to be permanent. Of course, if we didn't have to
worry about catalog bloat, the "clone" approach you're proposing would
be somewhat more attractive, but I actually think that the synergy is
in the other direction: the perfect place to store the catalog entries
and statistics for local temporary tables is - in a global temporary
table! Note that while a local temporary table can never inherit from
a permanent table, it's entirely sensible to let global temporary
tables inherit from permanent tables. Different backends will have
different views of the overall contents of the parent table, but
that's OK, even desirable.
...Robert
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-24 23:01 Robert Haas <robertmhaas@gmail.com>
parent: Simon Riggs <simon@2ndQuadrant.com>
0 siblings, 1 reply; 525+ messages in thread
From: Robert Haas @ 2010-04-24 23:01 UTC (permalink / raw)
To: Simon Riggs <simon@2ndquadrant.com>; +Cc: pgsql-hackers
On Sat, Apr 24, 2010 at 6:29 PM, Simon Riggs <simon@2ndquadrant.com> wrote:
> On Fri, 2010-04-23 at 22:52 -0400, Robert Haas wrote:
>> Thoughts?
>
> Only a requirement: that we design this in a way that will allow temp
> tables to be used during Hot Standby. I make not other comment.
For so long as local temporary tables put their catalog entries in
pg_class, we're not going to be able to use them during Hot Standby.
See the email I just sent elsewhere on this thread for a long term
roadmap to getting out of that pickle. At least under the
implementation I'm proposing here, making global temporary tables
usable would be an easier nut to crack, because the catalog entries
are a non-issue. There is one major problem, though: assigning a
scratch relfilenode to the temporary table requires generating an OID,
which we currently have no way to allow on the standby. Upthread I
also proposed an implementation for unlogged tables (that is, contents
don't survive a server bounce) which wouldn't have that problem
either, although I haven't fully thought it through and there may very
well be other issues.
So in short: I don't think anything we're talking about it would make
HS use harder, and some of it might make it easier. But probably some
additional engineering effort dedicated specifically to solving the
problems unique to HS would still be needed.
...Robert
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-25 00:47 Jim Nasby <decibel@decibel.org>
parent: Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 1 reply; 525+ messages in thread
From: Jim Nasby @ 2010-04-25 00:47 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Robert Haas <robertmhaas@gmail.com>; Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
On Apr 24, 2010, at 12:31 PM, Tom Lane wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
>> At least AIUI, the use case for this feature is that you want to avoid
>> creating "the same" temporary table over and over again.
>
> The context that I've seen it come up in is that people don't want to
> clutter their functions with create-it-if-it-doesn't-exist logic,
> which you have to have given the current behavior of temp tables.
> Any performance gain from reduced catalog churn would be gravy.
>
> Aside from the DROP problem, I think this implementation proposal
> has one other big shortcoming: what are you going to do about
> table statistics? In many cases, you really *have* to do an ANALYZE
> once you've populated a temp table, if you want to get decent plans
> for it. Where will you put those stats?
One possibility: rename the existing pg_stats to pg_stats_permanent. Create a global temporary table called pg_stats_temporary. pg_stats becomes a union of the two. I know the backend wouldn't be able to use the view, but hopefully access to statistics goes through a limited set of functions so that teaching them about the two different tables isn't hard.
As for cleanup and storage questions; what about having temp objects live in pgsql_tmp? I'm thinking create a directory under pgsql_tmp for a backend PID the first time it creates a temp object (global or local) and create the files in there. That also means that we don't have to come up with different relfilenodes for each backend. On the other hand, some layer (presumably smgr) would need to understand whether a relation was temporary or not. If we do that, cleanup is easy: you can remove any directories that no longer have a running PID. For forensics you probably only want to do that automatically when a backend starts and discovers it already has a directory, though we should also provide an administrator function that will clobber all directories that no longer have backends.
--
Jim C. Nasby, Database Architect jim@nasby.net
512.569.9461 (cell) http://jim.nasby.net
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-25 01:14 Robert Haas <robertmhaas@gmail.com>
parent: Jim Nasby <decibel@decibel.org>
0 siblings, 2 replies; 525+ messages in thread
From: Robert Haas @ 2010-04-25 01:14 UTC (permalink / raw)
To: Jim Nasby <decibel@decibel.org>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
On Sat, Apr 24, 2010 at 8:47 PM, Jim Nasby <decibel@decibel.org> wrote:
> On Apr 24, 2010, at 12:31 PM, Tom Lane wrote:
>> Robert Haas <robertmhaas@gmail.com> writes:
>>> At least AIUI, the use case for this feature is that you want to avoid
>>> creating "the same" temporary table over and over again.
>>
>> The context that I've seen it come up in is that people don't want to
>> clutter their functions with create-it-if-it-doesn't-exist logic,
>> which you have to have given the current behavior of temp tables.
>> Any performance gain from reduced catalog churn would be gravy.
>>
>> Aside from the DROP problem, I think this implementation proposal
>> has one other big shortcoming: what are you going to do about
>> table statistics? In many cases, you really *have* to do an ANALYZE
>> once you've populated a temp table, if you want to get decent plans
>> for it. Where will you put those stats?
>
> One possibility: rename the existing pg_stats to pg_stats_permanent. Create a global temporary table called pg_stats_temporary. pg_stats becomes a union of the two. I know the backend wouldn't be able to use the view, but hopefully access to statistics goes through a limited set of functions so that teaching them about the two different tables isn't hard.
Yeah, I don't think that would be too horrible. Part of me feels like
you'd want to have the ability to store stats for a global temp table
in either one of those tables depending on use-case, but I'm also
reluctant to invent a lot of new syntax for a very limited use case.
> As for cleanup and storage questions; what about having temp objects live in pgsql_tmp? I'm thinking create a directory under pgsql_tmp for a backend PID the first time it creates a temp object (global or local) and create the files in there. That also means that we don't have to come up with different relfilenodes for each backend.
That would impose a couple of implementation restrictions that don't
seem necessary. One, it would imply ignoring reltablespace. Two, it
would prohibit (or at least complicate) allowing a backend to CLUSTER
or REINDEX its own private copy of the rel.
> On the other hand, some layer (presumably smgr) would need to understand whether a relation was temporary or not. If we do that, cleanup is easy: you can remove any directories that no longer have a running PID. For forensics you probably only want to do that automatically when a backend starts and discovers it already has a directory, though we should also provide an administrator function that will clobber all directories that no longer have backends.
Unfortunately, I don't see much alternative to making smgr know
something about the temp-ness of the relation, though I'm hoping to
keep the smgr surgery to an absolute minimum. Maybe what we could do
is incorporate the backend ID or PID into the file name when the
relation is temp. Then we could scan for and nuke such files pretty
easily. Otherwise I can't really think how to make it work.
...Robert
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-25 01:37 Tom Lane <tgl@sss.pgh.pa.us>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 1 reply; 525+ messages in thread
From: Tom Lane @ 2010-04-25 01:37 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Jim Nasby <decibel@decibel.org>; Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
> Unfortunately, I don't see much alternative to making smgr know
> something about the temp-ness of the relation, though I'm hoping to
> keep the smgr surgery to an absolute minimum. Maybe what we could do
> is incorporate the backend ID or PID into the file name when the
> relation is temp. Then we could scan for and nuke such files pretty
> easily. Otherwise I can't really think how to make it work.
I think that could be a really good idea, mainly because it makes
post-crash cleanup MUCH safer: you can tell with certainty from the
filename that it's a leftover temp table. The notion of zapping files
just because we don't see them listed in pg_class has always scared the
heck out of me.
We already know temp-ness at pretty low levels, like bufmgr vs localbuf.
Pushing it all the way down to smgr doesn't seem like a leap; in fact
I think it would eliminate a separate isTemp parameter in a lot of places.
regards, tom lane
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-25 01:51 Jim Nasby <decibel@decibel.org>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 0 replies; 525+ messages in thread
From: Jim Nasby @ 2010-04-25 01:51 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
On Apr 24, 2010, at 8:14 PM, Robert Haas wrote:
>> One possibility: rename the existing pg_stats to pg_stats_permanent. Create a global temporary table called pg_stats_temporary. pg_stats becomes a union of the two. I know the backend wouldn't be able to use the view, but hopefully access to statistics goes through a limited set of functions so that teaching them about the two different tables isn't hard.
>
> Yeah, I don't think that would be too horrible. Part of me feels like
> you'd want to have the ability to store stats for a global temp table
> in either one of those tables depending on use-case, but I'm also
> reluctant to invent a lot of new syntax for a very limited use case.
Yeah, I'm thinking that's very probably overkill. And if we were going to go to that level, I think it would be far more useful to provide an interface to allow manual control over statistics first, so that you can give the optimizer custom information.
>> As for cleanup and storage questions; what about having temp objects live in pgsql_tmp? I'm thinking create a directory under pgsql_tmp for a backend PID the first time it creates a temp object (global or local) and create the files in there. That also means that we don't have to come up with different relfilenodes for each backend.
>
> That would impose a couple of implementation restrictions that don't
> seem necessary. One, it would imply ignoring reltablespace. Two, it
> would prohibit (or at least complicate) allowing a backend to CLUSTER
> or REINDEX its own private copy of the rel.
Well, the same structure could be imposed underneath a temptablespace. I don't think it matters where you ultimately put it, the goal is just to make sure you can definitively tell that a file was a: temporary and b: what PID it belonged to. That allows for safe cleanup.
--
Jim C. Nasby, Database Architect jim@nasby.net
512.569.9461 (cell) http://jim.nasby.net
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-25 01:59 Robert Haas <robertmhaas@gmail.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 525+ messages in thread
From: Robert Haas @ 2010-04-25 01:59 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Jim Nasby <decibel@decibel.org>; Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
On Sat, Apr 24, 2010 at 9:37 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
>> Unfortunately, I don't see much alternative to making smgr know
>> something about the temp-ness of the relation, though I'm hoping to
>> keep the smgr surgery to an absolute minimum. Maybe what we could do
>> is incorporate the backend ID or PID into the file name when the
>> relation is temp. Then we could scan for and nuke such files pretty
>> easily. Otherwise I can't really think how to make it work.
>
> I think that could be a really good idea, mainly because it makes
> post-crash cleanup MUCH safer: you can tell with certainty from the
> filename that it's a leftover temp table. The notion of zapping files
> just because we don't see them listed in pg_class has always scared the
> heck out of me.
>
> We already know temp-ness at pretty low levels, like bufmgr vs localbuf.
> Pushing it all the way down to smgr doesn't seem like a leap; in fact
> I think it would eliminate a separate isTemp parameter in a lot of places.
Eh? I don't see how it's going to do that.
...Robert
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-25 02:22 Robert Haas <robertmhaas@gmail.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Robert Haas @ 2010-04-25 02:22 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Jim Nasby <decibel@decibel.org>; Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
On Sat, Apr 24, 2010 at 9:59 PM, Robert Haas <robertmhaas@gmail.com> wrote:
> On Sat, Apr 24, 2010 at 9:37 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Robert Haas <robertmhaas@gmail.com> writes:
>>> Unfortunately, I don't see much alternative to making smgr know
>>> something about the temp-ness of the relation, though I'm hoping to
>>> keep the smgr surgery to an absolute minimum. Maybe what we could do
>>> is incorporate the backend ID or PID into the file name when the
>>> relation is temp. Then we could scan for and nuke such files pretty
>>> easily. Otherwise I can't really think how to make it work.
>>
>> I think that could be a really good idea, mainly because it makes
>> post-crash cleanup MUCH safer: you can tell with certainty from the
>> filename that it's a leftover temp table. The notion of zapping files
>> just because we don't see them listed in pg_class has always scared the
>> heck out of me.
>>
>> We already know temp-ness at pretty low levels, like bufmgr vs localbuf.
>> Pushing it all the way down to smgr doesn't seem like a leap; in fact
>> I think it would eliminate a separate isTemp parameter in a lot of places.
>
> Eh? I don't see how it's going to do that.
Oh, maybe I do see. If we pass it to smgropen() and stash it in the
SMgrRelation, we don't have to keep supplying it later on, maybe?
Will investigate further.
...Robert
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-25 02:28 Tom Lane <tgl@sss.pgh.pa.us>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Tom Lane @ 2010-04-25 02:28 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Jim Nasby <decibel@decibel.org>; Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
> Oh, maybe I do see. If we pass it to smgropen() and stash it in the
> SMgrRelation, we don't have to keep supplying it later on, maybe?
Right. I'm unsure whether we should push it into the RelFileNode
struct itself, but even having it in SMgrRelation ought to cut out
a few places where it now has to be passed separately.
regards, tom lane
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-25 02:57 Robert Haas <robertmhaas@gmail.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 525+ messages in thread
From: Robert Haas @ 2010-04-25 02:57 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Jim Nasby <decibel@decibel.org>; Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
On Sat, Apr 24, 2010 at 10:28 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
>> Oh, maybe I do see. If we pass it to smgropen() and stash it in the
>> SMgrRelation, we don't have to keep supplying it later on, maybe?
>
> Right. I'm unsure whether we should push it into the RelFileNode
> struct itself, but even having it in SMgrRelation ought to cut out
> a few places where it now has to be passed separately.
Pushing it into the RelFileNode has some advantages in terms of being
able to get at the information from everywhere, but one thing that
makes me think that's probably not a good decision is that we somtimes
WAL-log relfilenodes. And WAL-logging the value of the isTemp flag is
a waste, because if we're WAL-logging, it's zero.
...Robert
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-25 03:02 Tom Lane <tgl@sss.pgh.pa.us>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 2 replies; 525+ messages in thread
From: Tom Lane @ 2010-04-25 03:02 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Jim Nasby <decibel@decibel.org>; Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
> Pushing it into the RelFileNode has some advantages in terms of being
> able to get at the information from everywhere, but one thing that
> makes me think that's probably not a good decision is that we somtimes
> WAL-log relfilenodes. And WAL-logging the value of the isTemp flag is
> a waste, because if we're WAL-logging, it's zero.
Yeah. I think we also use RelFileNode as a hash tag in places, and
so adding a bool to it would be problematic for a couple of reasons:
possibly uninitialized pad bytes, and uselessly incorporating more bytes
into the hash calculation.
regards, tom lane
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-25 07:49 Simon Riggs <simon@2ndQuadrant.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Simon Riggs @ 2010-04-25 07:49 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: pgsql-hackers
On Sat, 2010-04-24 at 19:01 -0400, Robert Haas wrote:
> There is one major problem, though: assigning a
> scratch relfilenode to the temporary table requires generating an OID,
> which we currently have no way to allow on the standby.
Why not have an unlogged counter, which resets each system start, using
same datatype as an oid. There's no necessity for the relfilenode to be
an actual oid is there?
That way we could use it on standbys also.
--
Simon Riggs www.2ndQuadrant.com
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-25 10:50 Robert Haas <robertmhaas@gmail.com>
parent: Simon Riggs <simon@2ndQuadrant.com>
0 siblings, 1 reply; 525+ messages in thread
From: Robert Haas @ 2010-04-25 10:50 UTC (permalink / raw)
To: Simon Riggs <simon@2ndquadrant.com>; +Cc: pgsql-hackers
On Sun, Apr 25, 2010 at 3:49 AM, Simon Riggs <simon@2ndquadrant.com> wrote:
> On Sat, 2010-04-24 at 19:01 -0400, Robert Haas wrote:
>
>> There is one major problem, though: assigning a
>> scratch relfilenode to the temporary table requires generating an OID,
>> which we currently have no way to allow on the standby.
>
> Why not have an unlogged counter, which resets each system start, using
> same datatype as an oid. There's no necessity for the relfilenode to be
> an actual oid is there?
>
> That way we could use it on standbys also.
I don't think that quite works, because the standby might assign a
relfilenode number for a global temp table and then the master might
subsequently assign the same relfilenode number to a regular table.
We might be able to make that not matter, but it's far from obvious to
me that there are no gotchas there...
...Robert
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-25 12:46 Robert Haas <robertmhaas@gmail.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 0 replies; 525+ messages in thread
From: Robert Haas @ 2010-04-25 12:46 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Jim Nasby <decibel@decibel.org>; Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
On Sat, Apr 24, 2010 at 11:02 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
>> Pushing it into the RelFileNode has some advantages in terms of being
>> able to get at the information from everywhere, but one thing that
>> makes me think that's probably not a good decision is that we somtimes
>> WAL-log relfilenodes. And WAL-logging the value of the isTemp flag is
>> a waste, because if we're WAL-logging, it's zero.
>
> Yeah. I think we also use RelFileNode as a hash tag in places, and
> so adding a bool to it would be problematic for a couple of reasons:
> possibly uninitialized pad bytes, and uselessly incorporating more bytes
> into the hash calculation.
Right. I was thinking about the padding issue, too. I took a crack
at adding an isTemp argument to smgropen() and removing it everywhere
else, but it turns out this isn't as straightforward as it ought to be
because nbtsort.c, rewriteheap.c, and tablecmds.c feel entitled to
violate the abstraction layer by passing isTemp = true to smgrwrite()
and/or smgrextend() even when the real value is false, under the
assumption that the only thing isTemp is doing in those functions is
controlling fsync behavior. I think we'll have to replace the isTemp
argument to those functions with a boolean whose explicit charter is
to do what they're using it for.
Removing the isTemp from smgrtruncate() and smgrdounlink() argument
looks easy, though. WIP patch that takes it that far is attached.
[davidfetter: patch description for PWN is "WIP patch to push isTemp
down into the smgr layer"]
...Robert
Attachments:
[text/x-diff] smgropen_istemp_wip.patch (14.9K, ../../x2s603c8f071004250546k53ca5d58w5377d3d53430b602@mail.gmail.com/2-smgropen_istemp_wip.patch)
download | inline diff:
diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c
index d1f7bcc..9645c95 100644
--- a/src/backend/access/heap/visibilitymap.c
+++ b/src/backend/access/heap/visibilitymap.c
@@ -373,8 +373,7 @@ visibilitymap_truncate(Relation rel, BlockNumber nheapblocks)
}
/* Truncate the unused VM pages, and send smgr inval message */
- smgrtruncate(rel->rd_smgr, VISIBILITYMAP_FORKNUM, newnblocks,
- rel->rd_istemp);
+ smgrtruncate(rel->rd_smgr, VISIBILITYMAP_FORKNUM, newnblocks);
/*
* We might as well update the local smgr_vm_nblocks setting. smgrtruncate
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index 3cc9bdd..b318700 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -1317,13 +1317,13 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
}
for (i = 0; i < ndelrels; i++)
{
- SMgrRelation srel = smgropen(delrels[i]);
+ SMgrRelation srel = smgropen(delrels[i], false);
ForkNumber fork;
for (fork = 0; fork <= MAX_FORKNUM; fork++)
{
if (smgrexists(srel, fork))
- smgrdounlink(srel, fork, false, false);
+ smgrdounlink(srel, fork, false);
}
smgrclose(srel);
}
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index b88cff2..2c5800d 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -4434,7 +4434,8 @@ xact_redo_commit(xl_xact_commit *xlrec, TransactionId xid, XLogRecPtr lsn)
/* Make sure files supposed to be dropped are dropped */
for (i = 0; i < xlrec->nrels; i++)
{
- SMgrRelation srel = smgropen(xlrec->xnodes[i]);
+ /* We know this can't be a temp rel, since it is xlog'd. */
+ SMgrRelation srel = smgropen(xlrec->xnodes[i], false);
ForkNumber fork;
for (fork = 0; fork <= MAX_FORKNUM; fork++)
@@ -4442,7 +4443,7 @@ xact_redo_commit(xl_xact_commit *xlrec, TransactionId xid, XLogRecPtr lsn)
if (smgrexists(srel, fork))
{
XLogDropRelation(xlrec->xnodes[i], fork);
- smgrdounlink(srel, fork, false, true);
+ smgrdounlink(srel, fork, true);
}
}
smgrclose(srel);
@@ -4537,7 +4538,8 @@ xact_redo_abort(xl_xact_abort *xlrec, TransactionId xid)
/* Make sure files supposed to be dropped are dropped */
for (i = 0; i < xlrec->nrels; i++)
{
- SMgrRelation srel = smgropen(xlrec->xnodes[i]);
+ /* We know this can't be a temp rel, since it is xlog'd. */
+ SMgrRelation srel = smgropen(xlrec->xnodes[i], false);
ForkNumber fork;
for (fork = 0; fork <= MAX_FORKNUM; fork++)
@@ -4545,7 +4547,7 @@ xact_redo_abort(xl_xact_abort *xlrec, TransactionId xid)
if (smgrexists(srel, fork))
{
XLogDropRelation(xlrec->xnodes[i], fork);
- smgrdounlink(srel, fork, false, true);
+ smgrdounlink(srel, fork, true);
}
}
smgrclose(srel);
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 9ee2036..4462cfd 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -277,8 +277,8 @@ XLogReadBufferExtended(RelFileNode rnode, ForkNumber forknum,
Assert(blkno != P_NEW);
- /* Open the relation at smgr level */
- smgr = smgropen(rnode);
+ /* Open the relation at smgr level. It's xlog'd, thus not temp. */
+ smgr = smgropen(rnode, false);
/*
* Create the target file if it doesn't already exist. This lets us cope
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index ad376a1..a9602e6 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -103,7 +103,7 @@ RelationCreateStorage(RelFileNode rnode, bool istemp)
xl_smgr_create xlrec;
SMgrRelation srel;
- srel = smgropen(rnode);
+ srel = smgropen(rnode, istemp);
smgrcreate(srel, MAIN_FORKNUM, false);
if (!istemp)
@@ -283,7 +283,7 @@ RelationTruncate(Relation rel, BlockNumber nblocks)
}
/* Do the real work */
- smgrtruncate(rel->rd_smgr, MAIN_FORKNUM, nblocks, rel->rd_istemp);
+ smgrtruncate(rel->rd_smgr, MAIN_FORKNUM, nblocks);
}
/*
@@ -322,14 +322,11 @@ smgrDoPendingDeletes(bool isCommit)
SMgrRelation srel;
int i;
- srel = smgropen(pending->relnode);
+ srel = smgropen(pending->relnode, pending->isTemp);
for (i = 0; i <= MAX_FORKNUM; i++)
{
if (smgrexists(srel, i))
- smgrdounlink(srel,
- i,
- pending->isTemp,
- false);
+ smgrdounlink(srel, i, false);
}
smgrclose(srel);
}
@@ -456,7 +453,8 @@ smgr_redo(XLogRecPtr lsn, XLogRecord *record)
xl_smgr_create *xlrec = (xl_smgr_create *) XLogRecGetData(record);
SMgrRelation reln;
- reln = smgropen(xlrec->rnode);
+ /* We know this can't be a temp rel, since it is xlog'd. */
+ reln = smgropen(xlrec->rnode, false);
smgrcreate(reln, MAIN_FORKNUM, true);
}
else if (info == XLOG_SMGR_TRUNCATE)
@@ -465,7 +463,8 @@ smgr_redo(XLogRecPtr lsn, XLogRecord *record)
SMgrRelation reln;
Relation rel;
- reln = smgropen(xlrec->rnode);
+ /* We know this can't be a temp rel, since it is xlog'd. */
+ reln = smgropen(xlrec->rnode, false);
/*
* Forcibly create relation if it doesn't exist (which suggests that
@@ -475,7 +474,7 @@ smgr_redo(XLogRecPtr lsn, XLogRecord *record)
*/
smgrcreate(reln, MAIN_FORKNUM, true);
- smgrtruncate(reln, MAIN_FORKNUM, xlrec->blkno, false);
+ smgrtruncate(reln, MAIN_FORKNUM, xlrec->blkno);
/* Also tell xlogutils.c about it */
XLogTruncateRelation(xlrec->rnode, MAIN_FORKNUM, xlrec->blkno);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 25b2807..aea7cc5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -6981,7 +6981,7 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace)
newrnode = rel->rd_node;
newrnode.relNode = newrelfilenode;
newrnode.spcNode = newTableSpace;
- dstrel = smgropen(newrnode);
+ dstrel = smgropen(newrnode, rel->rd_istemp);
RelationOpenSmgr(rel);
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index caae936..f69e959 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -260,7 +260,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, bool isTemp,
{
bool hit;
- SMgrRelation smgr = smgropen(rnode);
+ SMgrRelation smgr = smgropen(rnode, isTemp);
return ReadBuffer_common(smgr, isTemp, forkNum, blockNum, mode, strategy,
&hit);
@@ -1838,7 +1838,7 @@ FlushBuffer(volatile BufferDesc *buf, SMgrRelation reln)
/* Find smgr relation for buffer */
if (reln == NULL)
- reln = smgropen(buf->tag.rnode);
+ reln = smgropen(buf->tag.rnode, false);
TRACE_POSTGRESQL_BUFFER_FLUSH_START(buf->tag.forkNum,
buf->tag.blockNum,
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index c5b6a2c..b036512 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -198,7 +198,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
SMgrRelation oreln;
/* Find smgr relation for buffer */
- oreln = smgropen(bufHdr->tag.rnode);
+ oreln = smgropen(bufHdr->tag.rnode, true);
/* And write... */
smgrwrite(oreln,
diff --git a/src/backend/storage/freespace/freespace.c b/src/backend/storage/freespace/freespace.c
index 579572f..b43a2ce 100644
--- a/src/backend/storage/freespace/freespace.c
+++ b/src/backend/storage/freespace/freespace.c
@@ -303,7 +303,7 @@ FreeSpaceMapTruncateRel(Relation rel, BlockNumber nblocks)
}
/* Truncate the unused FSM pages, and send smgr inval message */
- smgrtruncate(rel->rd_smgr, FSM_FORKNUM, new_nfsmblocks, rel->rd_istemp);
+ smgrtruncate(rel->rd_smgr, FSM_FORKNUM, new_nfsmblocks);
/*
* We might as well update the local smgr_fsm_nblocks setting.
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 4163ca0..63d049b 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -794,8 +794,7 @@ mdnblocks(SMgrRelation reln, ForkNumber forknum)
* mdtruncate() -- Truncate relation to specified number of blocks.
*/
void
-mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks,
- bool isTemp)
+mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks)
{
MdfdVec *v;
BlockNumber curnblk;
@@ -839,7 +838,7 @@ mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks,
errmsg("could not truncate file \"%s\": %m",
FilePathName(v->mdfd_vfd))));
- if (!isTemp)
+ if (!reln->isTemp)
register_dirty_segment(reln, forknum, v);
v = v->mdfd_chain;
Assert(ov != reln->md_fd[forknum]); /* we never drop the 1st
@@ -864,7 +863,7 @@ mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks,
errmsg("could not truncate file \"%s\" to %u blocks: %m",
FilePathName(v->mdfd_vfd),
nblocks)));
- if (!isTemp)
+ if (!reln->isTemp)
register_dirty_segment(reln, forknum, v);
v = v->mdfd_chain;
ov->mdfd_chain = NULL;
@@ -1051,8 +1050,12 @@ mdsync(void)
* we couldn't safely do that.) Furthermore, in many cases
* the relation will have been dirtied through this same smgr
* relation, and so we can save a file open/close cycle.
+ *
+ * We will never be asked to fsync a temporary relation, so
+ * it's safe to pass false as the second argument to
+ * smgropen().
*/
- reln = smgropen(entry->tag.rnode);
+ reln = smgropen(entry->tag.rnode, false);
/*
* It is possible that the relation has been dropped or
diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c
index 3b12cb3..cc72e5d 100644
--- a/src/backend/storage/smgr/smgr.c
+++ b/src/backend/storage/smgr/smgr.c
@@ -57,7 +57,7 @@ typedef struct f_smgr
BlockNumber blocknum, char *buffer, bool isTemp);
BlockNumber (*smgr_nblocks) (SMgrRelation reln, ForkNumber forknum);
void (*smgr_truncate) (SMgrRelation reln, ForkNumber forknum,
- BlockNumber nblocks, bool isTemp);
+ BlockNumber nblocks);
void (*smgr_immedsync) (SMgrRelation reln, ForkNumber forknum);
void (*smgr_pre_ckpt) (void); /* may be NULL */
void (*smgr_sync) (void); /* may be NULL */
@@ -131,7 +131,7 @@ smgrshutdown(int code, Datum arg)
* This does not attempt to actually open the object.
*/
SMgrRelation
-smgropen(RelFileNode rnode)
+smgropen(RelFileNode rnode, bool isTemp)
{
SMgrRelation reln;
bool found;
@@ -160,6 +160,7 @@ smgropen(RelFileNode rnode)
int forknum;
/* hash_search already filled in the lookup key */
+ reln->isTemp = isTemp;
reln->smgr_owner = NULL;
reln->smgr_targblock = InvalidBlockNumber;
reln->smgr_fsm_nblocks = InvalidBlockNumber;
@@ -323,7 +324,7 @@ smgrcreate(SMgrRelation reln, ForkNumber forknum, bool isRedo)
* already.
*/
void
-smgrdounlink(SMgrRelation reln, ForkNumber forknum, bool isTemp, bool isRedo)
+smgrdounlink(SMgrRelation reln, ForkNumber forknum, bool isRedo)
{
RelFileNode rnode = reln->smgr_rnode;
int which = reln->smgr_which;
@@ -331,7 +332,7 @@ smgrdounlink(SMgrRelation reln, ForkNumber forknum, bool isTemp, bool isRedo)
/* Close the fork */
(*(smgrsw[which].smgr_close)) (reln, forknum);
- smgr_internal_unlink(rnode, forknum, which, isTemp, isRedo);
+ smgr_internal_unlink(rnode, forknum, which, reln->isTemp, isRedo);
}
/*
@@ -455,14 +456,13 @@ smgrnblocks(SMgrRelation reln, ForkNumber forknum)
* The truncation is done immediately, so this can't be rolled back.
*/
void
-smgrtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks,
- bool isTemp)
+smgrtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks)
{
/*
* Get rid of any buffers for the about-to-be-deleted blocks. bufmgr will
* just drop them without bothering to write the contents.
*/
- DropRelFileNodeBuffers(reln->smgr_rnode, forknum, isTemp, nblocks);
+ DropRelFileNodeBuffers(reln->smgr_rnode, forknum, reln->isTemp, nblocks);
/*
* Send a shared-inval message to force other backends to close any smgr
@@ -479,8 +479,7 @@ smgrtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks,
/*
* Do the truncation.
*/
- (*(smgrsw[reln->smgr_which].smgr_truncate)) (reln, forknum, nblocks,
- isTemp);
+ (*(smgrsw[reln->smgr_which].smgr_truncate)) (reln, forknum, nblocks);
}
/*
diff --git a/src/include/storage/smgr.h b/src/include/storage/smgr.h
index cf248b8..6794c13 100644
--- a/src/include/storage/smgr.h
+++ b/src/include/storage/smgr.h
@@ -43,6 +43,9 @@ typedef struct SMgrRelationData
/* pointer to owning pointer, or NULL if none */
struct SMgrRelationData **smgr_owner;
+ /* is this a temporary relation? */
+ bool isTemp;
+
/*
* These next three fields are not actually used or manipulated by smgr,
* except that they are reset to InvalidBlockNumber upon a cache flush
@@ -70,7 +73,7 @@ typedef SMgrRelationData *SMgrRelation;
extern void smgrinit(void);
-extern SMgrRelation smgropen(RelFileNode rnode);
+extern SMgrRelation smgropen(RelFileNode rnode, bool isTemp);
extern bool smgrexists(SMgrRelation reln, ForkNumber forknum);
extern void smgrsetowner(SMgrRelation *owner, SMgrRelation reln);
extern void smgrclose(SMgrRelation reln);
@@ -78,7 +81,7 @@ extern void smgrcloseall(void);
extern void smgrclosenode(RelFileNode rnode);
extern void smgrcreate(SMgrRelation reln, ForkNumber forknum, bool isRedo);
extern void smgrdounlink(SMgrRelation reln, ForkNumber forknum,
- bool isTemp, bool isRedo);
+ bool isRedo);
extern void smgrextend(SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum, char *buffer, bool isTemp);
extern void smgrprefetch(SMgrRelation reln, ForkNumber forknum,
@@ -89,7 +92,7 @@ extern void smgrwrite(SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum, char *buffer, bool isTemp);
extern BlockNumber smgrnblocks(SMgrRelation reln, ForkNumber forknum);
extern void smgrtruncate(SMgrRelation reln, ForkNumber forknum,
- BlockNumber nblocks, bool isTemp);
+ BlockNumber nblocks);
extern void smgrimmedsync(SMgrRelation reln, ForkNumber forknum);
extern void smgrpreckpt(void);
extern void smgrsync(void);
@@ -114,7 +117,7 @@ extern void mdwrite(SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum, char *buffer, bool isTemp);
extern BlockNumber mdnblocks(SMgrRelation reln, ForkNumber forknum);
extern void mdtruncate(SMgrRelation reln, ForkNumber forknum,
- BlockNumber nblocks, bool isTemp);
+ BlockNumber nblocks);
extern void mdimmedsync(SMgrRelation reln, ForkNumber forknum);
extern void mdpreckpt(void);
extern void mdsync(void);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 444e892..da8a46b 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,7 +347,8 @@ typedef struct StdRdOptions
#define RelationOpenSmgr(relation) \
do { \
if ((relation)->rd_smgr == NULL) \
- smgrsetowner(&((relation)->rd_smgr), smgropen((relation)->rd_node)); \
+ smgrsetowner(&((relation)->rd_smgr), smgropen((relation)->rd_node, \
+ (relation)->rd_istemp)); \
} while (0)
/*
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-25 12:57 Simon Riggs <simon@2ndQuadrant.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Simon Riggs @ 2010-04-25 12:57 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: pgsql-hackers
On Sun, 2010-04-25 at 06:50 -0400, Robert Haas wrote:
> On Sun, Apr 25, 2010 at 3:49 AM, Simon Riggs <simon@2ndquadrant.com> wrote:
> > On Sat, 2010-04-24 at 19:01 -0400, Robert Haas wrote:
> >
> >> There is one major problem, though: assigning a
> >> scratch relfilenode to the temporary table requires generating an OID,
> >> which we currently have no way to allow on the standby.
> >
> > Why not have an unlogged counter, which resets each system start, using
> > same datatype as an oid. There's no necessity for the relfilenode to be
> > an actual oid is there?
> >
> > That way we could use it on standbys also.
>
> I don't think that quite works, because the standby might assign a
> relfilenode number for a global temp table and then the master might
> subsequently assign the same relfilenode number to a regular table.
> We might be able to make that not matter, but it's far from obvious to
> me that there are no gotchas there...
That sounds fairly simple to solve.
All I am saying is please include "working on the standby" as part of
your requirement.
--
Simon Riggs www.2ndQuadrant.com
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-25 14:26 Jim Nasby <decibel@decibel.org>
parent: Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 1 reply; 525+ messages in thread
From: Jim Nasby @ 2010-04-25 14:26 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Robert Haas <robertmhaas@gmail.com>; Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
On Apr 24, 2010, at 10:02 PM, Tom Lane wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
>> Pushing it into the RelFileNode has some advantages in terms of being
>> able to get at the information from everywhere, but one thing that
>> makes me think that's probably not a good decision is that we somtimes
>> WAL-log relfilenodes. And WAL-logging the value of the isTemp flag is
>> a waste, because if we're WAL-logging, it's zero.
>
> Yeah. I think we also use RelFileNode as a hash tag in places, and
> so adding a bool to it would be problematic for a couple of reasons:
> possibly uninitialized pad bytes, and uselessly incorporating more bytes
> into the hash calculation.
Do we need to hash it that frequently? Do we insert tons of them into WAL? Worrying about those cases smells like premature optimization, but admittedly I don't have enough knowledge here...
--
Jim C. Nasby, Database Architect jim@nasby.net
512.569.9461 (cell) http://jim.nasby.net
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-25 15:35 Robert Haas <robertmhaas@gmail.com>
parent: Simon Riggs <simon@2ndQuadrant.com>
0 siblings, 1 reply; 525+ messages in thread
From: Robert Haas @ 2010-04-25 15:35 UTC (permalink / raw)
To: Simon Riggs <simon@2ndquadrant.com>; +Cc: pgsql-hackers
On Sun, Apr 25, 2010 at 8:57 AM, Simon Riggs <simon@2ndquadrant.com> wrote:
>> I don't think that quite works, because the standby might assign a
>> relfilenode number for a global temp table and then the master might
>> subsequently assign the same relfilenode number to a regular table.
>> We might be able to make that not matter, but it's far from obvious to
>> me that there are no gotchas there...
>
> That sounds fairly simple to solve.
>
> All I am saying is please include "working on the standby" as part of
> your requirement.
Well, I think I already basically stated my position on this, but let
me try to be more clear. I'm not promising to work on any portion of
this project AT ALL or to have it done at any particular time. I am
specifically not promising to do the extra work required to make it
work with Hot Standby, although I am also not saying that I won't.
Nobody has offered to fund any portion of this work, so there are no
guarantees, full stop. Even if you could successfully convince a
critical mass of people on this list that the work should not be
committed without adding support for temp tables in Hot Standby mode,
the most likely result of that would be that I would give up and not
pursue this at all, rather than that I would agree to do that in
addition to solving all the problems already discussed. And I don't
think you can even get that far, because I don't think too many people
here are going to say that we shouldn't add global temporary tables
unless we can also make them work with Hot Standby.
In all honesty, I would think that you would be happy about my
possibly implementing a flavor of temporary tables that would be
substantially more feasible to make work with Hot Standby than the
kind we have now, rather than (as you seem to be) complaining that I'm
not solving the entire problem.
...Robert
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-25 19:02 Tom Lane <tgl@sss.pgh.pa.us>
parent: Jim Nasby <decibel@decibel.org>
0 siblings, 0 replies; 525+ messages in thread
From: Tom Lane @ 2010-04-25 19:02 UTC (permalink / raw)
To: Jim Nasby <decibel@decibel.org>; +Cc: Robert Haas <robertmhaas@gmail.com>; Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
Jim Nasby <decibel@decibel.org> writes:
> On Apr 24, 2010, at 10:02 PM, Tom Lane wrote:
>> Yeah. I think we also use RelFileNode as a hash tag in places, and
>> so adding a bool to it would be problematic for a couple of reasons:
>> possibly uninitialized pad bytes, and uselessly incorporating more bytes
>> into the hash calculation.
> Do we need to hash it that frequently?
Every buffer access, for instance.
> Do we insert tons of them into WAL?
Yes; the vast majority of WAL records contain one.
regards, tom lane
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-25 21:29 Josh Berkus <josh@agliodbs.com>
parent: Robert Haas <robertmhaas@gmail.com>
3 siblings, 2 replies; 525+ messages in thread
From: Josh Berkus @ 2010-04-25 21:29 UTC (permalink / raw)
To: pgsql-hackers
Robert,
> (1). What I *think* it is supposed to mean is that the table is a
> permanent object which is "globally" visible - that is, it's part of
> some non-temp schema like public or $user and it's column definitions
> etc. are visible to all backends - and it's not automatically removed
> on commit, backend exit, etc. - but the *contents* of the table are
> temporary and backend-local, so that each new backend initially sees
> it as empty and can then insert, update, and delete data independently
> of what any other backend does.
While closer to the standard, the above definition is a lot less useful
than what I believe a lot of people want, which is a table which is
globally visible, but has no durability; that is, it does not get
WAL-logged or recovered on restart. Certainly this latter definition
would be far more useful to support materialized views.
--
-- Josh Berkus
PostgreSQL Experts Inc.
http://www.pgexperts.com
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-25 21:51 Andrew Dunstan <andrew@dunslane.net>
parent: Josh Berkus <josh@agliodbs.com>
1 sibling, 0 replies; 525+ messages in thread
From: Andrew Dunstan @ 2010-04-25 21:51 UTC (permalink / raw)
To: Josh Berkus <josh@agliodbs.com>; +Cc: pgsql-hackers
Josh Berkus wrote:
> Robert,
>
>> (1). What I *think* it is supposed to mean is that the table is a
>> permanent object which is "globally" visible - that is, it's part of
>> some non-temp schema like public or $user and it's column definitions
>> etc. are visible to all backends - and it's not automatically removed
>> on commit, backend exit, etc. - but the *contents* of the table are
>> temporary and backend-local, so that each new backend initially sees
>> it as empty and can then insert, update, and delete data independently
>> of what any other backend does.
>
> While closer to the standard, the above definition is a lot less
> useful than what I believe a lot of people want, which is a table
> which is globally visible, but has no durability; that is, it does not
> get WAL-logged or recovered on restart. Certainly this latter
> definition would be far more useful to support materialized views.
These are not mutually exclusive features. What you're asking for has
value, certainly, but it's not a temp table in the standard's terms
(which is a feature that also has value, I believe).
cheers
andrew
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-25 21:54 Robert Haas <robertmhaas@gmail.com>
parent: Josh Berkus <josh@agliodbs.com>
1 sibling, 0 replies; 525+ messages in thread
From: Robert Haas @ 2010-04-25 21:54 UTC (permalink / raw)
To: Josh Berkus <josh@agliodbs.com>; +Cc: pgsql-hackers
On Sun, Apr 25, 2010 at 5:29 PM, Josh Berkus <josh@agliodbs.com> wrote:
> Robert,
>
>> (1). What I *think* it is supposed to mean is that the table is a
>> permanent object which is "globally" visible - that is, it's part of
>> some non-temp schema like public or $user and it's column definitions
>> etc. are visible to all backends - and it's not automatically removed
>> on commit, backend exit, etc. - but the *contents* of the table are
>> temporary and backend-local, so that each new backend initially sees
>> it as empty and can then insert, update, and delete data independently
>> of what any other backend does.
>
> While closer to the standard, the above definition is a lot less useful than
> what I believe a lot of people want, which is a table which is globally
> visible, but has no durability; that is, it does not get WAL-logged or
> recovered on restart. Certainly this latter definition would be far more
> useful to support materialized views.
I think it's arguable which one is more useful, but I think a good
deal of the infrastructure can be made to serve both purposes, as I
further expounded upon here.
http://archives.postgresql.org/pgsql-hackers/2010-04/msg01123.php
...Robert
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-26 19:30 Simon Riggs <simon@2ndQuadrant.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 2 replies; 525+ messages in thread
From: Simon Riggs @ 2010-04-26 19:30 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: pgsql-hackers
On Sun, 2010-04-25 at 11:35 -0400, Robert Haas wrote:
> And I don't
> think you can even get that far, because I don't think too many people
> here are going to say that we shouldn't add global temporary tables
> unless we can also make them work with Hot Standby.
The policy round here for some time has been that when we implement
things we make them work fully and seamlessly. I don't see why Hot
Standby would be singled out any more than any other feature, say
Windows support or tablespaces should be occasionally ignored.
People need to get used to the new feature set, just as we had to with
HOT, subtransactions, prepared transactions, Gist etc.. That may require
a thwack from various people, but the responsibility lies with the new
feature implementor, not the person supporting existing code.
I fully understand your wish to implement a partial feature with caveats
because I have argued that many times myself. But I've come to realise
that the best way is to build things so they work cleanly across the
board. Other developers can plan projects in the knowledge that they can
build directly on firm foundations, not fill in the cracks. In the end
this comes down to a choice as developers, do we help each other by
doing a full job, or do we leave unexploded bombs for each other through
short-termism? Now I understand this better myself, I act differently
and accept objections if people think a fuller, more complete design is
what is needed. Recent demonstrations of that available, both objecting
and accepting.
Don't see this as an extra task, just see it as one of the many aspects
that will need to be considered when developing it. If you do that it
need not be additional work.
--
Simon Riggs www.2ndQuadrant.com
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-26 19:40 Tom Lane <tgl@sss.pgh.pa.us>
parent: Simon Riggs <simon@2ndQuadrant.com>
1 sibling, 1 reply; 525+ messages in thread
From: Tom Lane @ 2010-04-26 19:40 UTC (permalink / raw)
To: Simon Riggs <simon@2ndQuadrant.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
Simon Riggs <simon@2ndQuadrant.com> writes:
> On Sun, 2010-04-25 at 11:35 -0400, Robert Haas wrote:
>> And I don't
>> think you can even get that far, because I don't think too many people
>> here are going to say that we shouldn't add global temporary tables
>> unless we can also make them work with Hot Standby.
> The policy round here for some time has been that when we implement
> things we make them work fully and seamlessly. I don't see why Hot
> Standby would be singled out any more than any other feature, say
> Windows support or tablespaces should be occasionally ignored.
The current definition of Hot Standby is that it's a *read only*
behavior. Not read mostly. What you are proposing is a rather
fundamental change in the behavior of HS, and it doesn't seem to me
that it should be on the head of anybody else to make it work.
IOW: I agree with Robert that this is not an essential part of global
temp tables. If it happens to fall out that it works like that, great,
but it isn't a requirement.
regards, tom lane
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-26 19:50 Simon Riggs <simon@2ndQuadrant.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 525+ messages in thread
From: Simon Riggs @ 2010-04-26 19:50 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
On Mon, 2010-04-26 at 15:40 -0400, Tom Lane wrote:
> Simon Riggs <simon@2ndQuadrant.com> writes:
> > On Sun, 2010-04-25 at 11:35 -0400, Robert Haas wrote:
> >> And I don't
> >> think you can even get that far, because I don't think too many people
> >> here are going to say that we shouldn't add global temporary tables
> >> unless we can also make them work with Hot Standby.
>
> > The policy round here for some time has been that when we implement
> > things we make them work fully and seamlessly. I don't see why Hot
> > Standby would be singled out any more than any other feature, say
> > Windows support or tablespaces should be occasionally ignored.
>
> The current definition of Hot Standby is that it's a *read only*
> behavior. Not read mostly. What you are proposing is a rather
> fundamental change in the behavior of HS, and it doesn't seem to me
> that it should be on the head of anybody else to make it work.
That's a dangerous precedent you just set.
--
Simon Riggs www.2ndQuadrant.com
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-26 20:02 Robert Haas <robertmhaas@gmail.com>
parent: Simon Riggs <simon@2ndQuadrant.com>
1 sibling, 0 replies; 525+ messages in thread
From: Robert Haas @ 2010-04-26 20:02 UTC (permalink / raw)
To: Simon Riggs <simon@2ndquadrant.com>; +Cc: pgsql-hackers
On Mon, Apr 26, 2010 at 3:30 PM, Simon Riggs <simon@2ndquadrant.com> wrote:
> On Sun, 2010-04-25 at 11:35 -0400, Robert Haas wrote:
>
>> And I don't
>> think you can even get that far, because I don't think too many people
>> here are going to say that we shouldn't add global temporary tables
>> unless we can also make them work with Hot Standby.
>
> The policy round here for some time has been that when we implement
> things we make them work fully and seamlessly. I don't see why Hot
> Standby would be singled out any more than any other feature, say
> Windows support or tablespaces should be occasionally ignored.
>
> People need to get used to the new feature set, just as we had to with
> HOT, subtransactions, prepared transactions, Gist etc.. That may require
> a thwack from various people, but the responsibility lies with the new
> feature implementor, not the person supporting existing code.
>
> I fully understand your wish to implement a partial feature with caveats
> because I have argued that many times myself. But I've come to realise
> that the best way is to build things so they work cleanly across the
> board. Other developers can plan projects in the knowledge that they can
> build directly on firm foundations, not fill in the cracks. In the end
> this comes down to a choice as developers, do we help each other by
> doing a full job, or do we leave unexploded bombs for each other through
> short-termism? Now I understand this better myself, I act differently
> and accept objections if people think a fuller, more complete design is
> what is needed. Recent demonstrations of that available, both objecting
> and accepting.
>
> Don't see this as an extra task, just see it as one of the many aspects
> that will need to be considered when developing it. If you do that it
> need not be additional work.
I think you're looking at this the wrong way. If temporary tables
have to work with Hot Standby in order for it to be committable, then
we should never have committed Hot Standby in the first place because
our current flavor of temporary tables doesn't. Was that an oversight
on your part, or a recognition that you can't solve every problem in
one commit?
...Robert
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-26 20:06 Tom Lane <tgl@sss.pgh.pa.us>
parent: Simon Riggs <simon@2ndQuadrant.com>
0 siblings, 0 replies; 525+ messages in thread
From: Tom Lane @ 2010-04-26 20:06 UTC (permalink / raw)
To: Simon Riggs <simon@2ndQuadrant.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
Simon Riggs <simon@2ndQuadrant.com> writes:
> On Mon, 2010-04-26 at 15:40 -0400, Tom Lane wrote:
>> The current definition of Hot Standby is that it's a *read only*
>> behavior. Not read mostly. What you are proposing is a rather
>> fundamental change in the behavior of HS, and it doesn't seem to me
>> that it should be on the head of anybody else to make it work.
> That's a dangerous precedent you just set.
[ shrug... ] If you have near-term solutions for all the *other*
problems that would be involved (like what XID to put into rows you
insert in the temp tables) then I might think that what you're asking
Robert to do is reasonable. Personally I think non-read-only HS is
entirely pie in the sky, and therefore it's not reasonable to saddle
unrelated development tasks with an expectation that they should work
with a behavior that probably won't ever happen.
regards, tom lane
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: global temporary tables
@ 2010-04-26 20:18 Alvaro Herrera <alvherre@commandprompt.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 0 replies; 525+ messages in thread
From: Alvaro Herrera @ 2010-04-26 20:18 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Robert Haas <robertmhaas@gmail.com>; Greg Sabino Mullane <greg@turnstep.com>; pgsql-hackers
Tom Lane escribió:
> [ forgot to respond to this part ]
>
> Robert Haas <robertmhaas@gmail.com> writes:
> > ... I don't see the problem with DROP.
> > Under the proposed design, it's approximately equivalent to dropping a
> > table that someone else has truncated. You just wait for the
> > necessary lock and then do it.
>
> And do *what*? You can remove the catalog entries, but how are you
> going to make the physical storage of other backends' versions go away?
> (To say nothing of making them flush their local buffers for it.)
Maybe we could add a sinval message to that effect.
--
Alvaro Herrera http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
^ permalink raw reply [nested|flat] 525+ messages in thread
* Global temporary tables
@ 2019-07-31 15:05 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-07-31 15:05 UTC (permalink / raw)
To: pgsql-hackers
Current Postgres implementation of temporary table causes number of
problems:
1. Catalog bloating: if client creates and deletes too many temporary
tables, then autovacuum get stuck on catalog.
2. Parallel queries: right now usage of temporary tables in query
disables parallel plan.
3. It is not possible to use temporary tables at replica. Hot standby
configuration is frequently used to run OLAP queries on replica
and results of such queries are used to be saved in temporary tables.
Right now it is not possible (except "hackers" solution with storing
results in file_fdw).
4. Temporary tables can not be used in prepared transactions.
5. Inefficient memory usage and possible memory overflow: each backend
maintains its own local buffers for work with temporary tables.
Default size of temporary buffers is 8Mb. It seems to be too small for
modern servers having hundreds of gigabytes of RAM, causing extra
copying of data
between OS cache and local buffers. But if there are thousands of
backends, each executing queries with temporary tables, then total
amount of
memory used for temporary buffers can exceed several tens of gigabytes.
6. Connection pooler can not reschedule session which has created
temporary tables to some other backend
because it's data is stored in local buffers.
There were several attempts to address this problems.
For example Alexandr Alekseev has implemented patch which allows to
create fast temporary tables without accessing system catalog:
https://www.postgresql.org/message-id/flat/20160301182500.2c81c3dc%40fujitsu
Unfortunately this patch was too invasive and rejected by community.
There was also attempt to allow under some condition use temporary
tables in 2PC transactions:
https://www.postgresql.org/message-id/flat/m2d0pllvqy.fsf%40dimitris-macbook-pro.home
https://www.postgresql.org/message-id/flat/3a4b3c88-4fa5-1edb-a878-1ed76fa1c82b%40postgrespro.ru#d8a...
Them were also rejected.
I try to make yet another attempt to address this problems, first of all
1), 2), 5) and 6)
To solve this problems I propose notion of "global temporary" tables,
similar with ones in Oracle.
Definition of this table (metadata) is shared by all backends but data
is private to the backend. After session termination data is obviously lost.
Suggested syntax for creation of global temporary tables:
create global temp table
or
create session table
Once been created it can be used by all backends.
Global temporary tables are accessed though shared buffers (to solve
problem 2).
Cleanup of temporary tables data (release of shared buffer and deletion
of relation files) is performed on backend termination.
In case of abnormal server termination, files of global temporary tables
are cleaned-up in the same way as of local temporary tables.
Certainly there are cases were global temporary tables can not be used,
i.e. when application is dynamically constructed name and columns of
temporary table.
Also access to local buffers is more efficient than access to shared
buffers because it doesn't require any synchronization.
But please notice that it is always possible to create old (local)
temporary tables which preserves current behavior.
The problem with replica is still not solved. But shared metadata is
step in this direction.
I am thinking about reimplementation of temporary tables using new table
access method API.
The drawback of such approach is that it will be necessary to
reimplement large bulk of heapam code.
But this approach allows to eliminate visibility check for temporary
table tuples and decrease size of tuple header.
I still not sure if implementing special table access method for
temporary tables is good idea.
Patch for global temporary tables is attached to this mail.
The known limitation is that now it supports only B-Tree indexes.
Any feedback is welcome.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
[text/x-patch] session_tables.patch (41.0K, ../../73954ab7-44d3-b37b-81a3-69bdcbb446f7@postgrespro.ru/2-session_tables.patch)
download | inline diff:
commit 4d7a1f51405cdcb4d9dc421b0cb3a0d3439ba362
Author: Konstantin Knizhnik <knizhnik@garret.ru>
Date: Tue Jun 25 14:49:50 2019 +0300
Add session tables
diff --git a/src/backend/access/gin/ginxlog.c b/src/backend/access/gin/ginxlog.c
index c945b28..14d4e48 100644
--- a/src/backend/access/gin/ginxlog.c
+++ b/src/backend/access/gin/ginxlog.c
@@ -95,13 +95,13 @@ ginRedoInsertEntry(Buffer buffer, bool isLeaf, BlockNumber rightblkno, void *rda
if (PageAddItem(page, (Item) itup, IndexTupleSize(itup), offset, false, false) == InvalidOffsetNumber)
{
- RelFileNode node;
+ RelFileNodeBackend rnode;
ForkNumber forknum;
BlockNumber blknum;
- BufferGetTag(buffer, &node, &forknum, &blknum);
+ BufferGetTag(buffer, &rnode, &forknum, &blknum);
elog(ERROR, "failed to add item to index page in %u/%u/%u",
- node.spcNode, node.dbNode, node.relNode);
+ rnode.node.spcNode, rnode.node.dbNode, rnode.node.relNode);
}
}
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 92ea1d1..2994687 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -674,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);
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index de4d4ef..c96b722 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -804,7 +804,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 && PageIsNew(BufferGetPage(buf)) && IsSessionRelationBackendId(rel->rd_backend))
+ _bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
+ else
+ _bt_checkpage(rel, buf);
}
else
{
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index d3c0a93..5e2dd63 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -213,6 +213,7 @@ void
XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
{
registered_buffer *regbuf;
+ RelFileNodeBackend rnode;
/* NO_IMAGE doesn't make sense with FORCE_IMAGE */
Assert(!((flags & REGBUF_FORCE_IMAGE) && (flags & (REGBUF_NO_IMAGE))));
@@ -227,7 +228,8 @@ XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
regbuf = ®istered_buffers[block_id];
- BufferGetTag(buffer, ®buf->rnode, ®buf->forkno, ®buf->block);
+ BufferGetTag(buffer, &rnode, ®buf->forkno, ®buf->block);
+ regbuf->rnode = rnode.node;
regbuf->page = BufferGetPage(buffer);
regbuf->flags = flags;
regbuf->rdata_tail = (XLogRecData *) ®buf->rdata_head;
@@ -919,7 +921,7 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std)
int flags;
PGAlignedBlock copied_buffer;
char *origdata = (char *) BufferGetBlock(buffer);
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forkno;
BlockNumber blkno;
@@ -948,7 +950,7 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std)
flags |= REGBUF_STANDARD;
BufferGetTag(buffer, &rnode, &forkno, &blkno);
- XLogRegisterBlock(0, &rnode, forkno, blkno, copied_buffer.data, flags);
+ XLogRegisterBlock(0, &rnode.node, forkno, blkno, copied_buffer.data, flags);
recptr = XLogInsert(RM_XLOG_ID, XLOG_FPI_FOR_HINT);
}
@@ -1009,7 +1011,7 @@ XLogRecPtr
log_newpage_buffer(Buffer buffer, bool page_std)
{
Page page = BufferGetPage(buffer);
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forkNum;
BlockNumber blkno;
@@ -1018,7 +1020,7 @@ log_newpage_buffer(Buffer buffer, bool page_std)
BufferGetTag(buffer, &rnode, &forkNum, &blkno);
- return log_newpage(&rnode, forkNum, blkno, page, page_std);
+ return log_newpage(&rnode.node, forkNum, blkno, page, page_std);
}
/*
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 11936a6..82108d3 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -409,6 +409,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 d2e4f53..8709ff1 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3550,7 +3550,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 3cc886f..a111ddc 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 ebaec4f..2719e2a 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/tablecmds.c b/src/backend/commands/tablecmds.c
index 95af5ec..788d6cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -7617,6 +7617,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,
@@ -13968,6 +13974,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 */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 8311b1d..3f011c6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3265,20 +3265,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/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index e7c32f2..d19d7bc 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -3494,6 +3494,7 @@ ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
{
ReorderBufferTupleCidKey key;
ReorderBufferTupleCidEnt *ent;
+ RelFileNodeBackend rnode;
ForkNumber forkno;
BlockNumber blockno;
bool updated_mapping = false;
@@ -3507,7 +3508,8 @@ ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
* get relfilenode from the buffer, no convenient way to access it other
* than that.
*/
- BufferGetTag(buffer, &key.relnode, &forkno, &blockno);
+ BufferGetTag(buffer, &rnode, &forkno, &blockno);
+ key.relnode = rnode.node;
/* tuples can only be in the main fork */
Assert(forkno == MAIN_FORKNUM);
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 7332e6b..44213f5 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -555,7 +555,7 @@ PrefetchBuffer(Relation reln, ForkNumber forkNum, BlockNumber blockNum)
int buf_id;
/* create a tag so we can lookup the buffer */
- INIT_BUFFERTAG(newTag, reln->rd_smgr->smgr_rnode.node,
+ INIT_BUFFERTAG(newTag, reln->rd_smgr->smgr_rnode,
forkNum, blockNum);
/* determine its hash code and partition lock ID */
@@ -709,7 +709,7 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
Block bufBlock;
bool found;
bool isExtend;
- bool isLocalBuf = SmgrIsTemp(smgr);
+ bool isLocalBuf = SmgrIsTemp(smgr) && relpersistence == RELPERSISTENCE_TEMP;
*hit = false;
@@ -1009,7 +1009,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
uint32 buf_state;
/* create a tag so we can lookup the buffer */
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* determine its hash code and partition lock ID */
newHash = BufTableHashCode(&newTag);
@@ -1531,7 +1531,8 @@ ReleaseAndReadBuffer(Buffer buffer,
{
bufHdr = GetLocalBufferDescriptor(-buffer - 1);
if (bufHdr->tag.blockNum == blockNum &&
- RelFileNodeEquals(bufHdr->tag.rnode, relation->rd_node) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, relation->rd_node) &&
+ bufHdr->tag.rnode.backend == relation->rd_backend &&
bufHdr->tag.forkNum == forkNum)
return buffer;
ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
@@ -1542,7 +1543,8 @@ ReleaseAndReadBuffer(Buffer buffer,
bufHdr = GetBufferDescriptor(buffer - 1);
/* we have pin, so it's ok to examine tag without spinlock */
if (bufHdr->tag.blockNum == blockNum &&
- RelFileNodeEquals(bufHdr->tag.rnode, relation->rd_node) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, relation->rd_node) &&
+ bufHdr->tag.rnode.backend == relation->rd_backend &&
bufHdr->tag.forkNum == forkNum)
return buffer;
UnpinBuffer(bufHdr, true);
@@ -1844,8 +1846,8 @@ BufferSync(int flags)
item = &CkptBufferIds[num_to_scan++];
item->buf_id = buf_id;
- item->tsId = bufHdr->tag.rnode.spcNode;
- item->relNode = bufHdr->tag.rnode.relNode;
+ item->tsId = bufHdr->tag.rnode.node.spcNode;
+ item->relNode = bufHdr->tag.rnode.node.relNode;
item->forkNum = bufHdr->tag.forkNum;
item->blockNum = bufHdr->tag.blockNum;
}
@@ -2558,7 +2560,7 @@ PrintBufferLeakWarning(Buffer buffer)
}
/* theoretically we should lock the bufhdr here */
- path = relpathbackend(buf->tag.rnode, backend, buf->tag.forkNum);
+ path = relpathbackend(buf->tag.rnode.node, backend, buf->tag.forkNum);
buf_state = pg_atomic_read_u32(&buf->state);
elog(WARNING,
"buffer refcount leak: [%03d] "
@@ -2630,7 +2632,7 @@ BufferGetBlockNumber(Buffer buffer)
* a buffer.
*/
void
-BufferGetTag(Buffer buffer, RelFileNode *rnode, ForkNumber *forknum,
+BufferGetTag(Buffer buffer, RelFileNodeBackend *rnode, ForkNumber *forknum,
BlockNumber *blknum)
{
BufferDesc *bufHdr;
@@ -2695,7 +2697,7 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
/* Find smgr relation for buffer */
if (reln == NULL)
- reln = smgropen(buf->tag.rnode, InvalidBackendId);
+ reln = smgropen(buf->tag.rnode.node, buf->tag.rnode.backend);
TRACE_POSTGRESQL_BUFFER_FLUSH_START(buf->tag.forkNum,
buf->tag.blockNum,
@@ -2929,7 +2931,7 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber forkNum,
int i;
/* If it's a local relation, it's localbuf.c's problem. */
- if (RelFileNodeBackendIsTemp(rnode))
+ if (RelFileNodeBackendIsLocalTemp(rnode))
{
if (rnode.backend == MyBackendId)
DropRelFileNodeLocalBuffers(rnode.node, forkNum, firstDelBlock);
@@ -2957,11 +2959,11 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber forkNum,
* We could check forkNum and blockNum as well as the rnode, but the
* incremental win from doing so seems small.
*/
- if (!RelFileNodeEquals(bufHdr->tag.rnode, rnode.node))
+ if (!RelFileNodeBackendEquals(bufHdr->tag.rnode, rnode))
continue;
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rnode.node) &&
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, rnode) &&
bufHdr->tag.forkNum == forkNum &&
bufHdr->tag.blockNum >= firstDelBlock)
InvalidateBuffer(bufHdr); /* releases spinlock */
@@ -2984,24 +2986,24 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
{
int i,
n = 0;
- RelFileNode *nodes;
+ RelFileNodeBackend *nodes;
bool use_bsearch;
if (nnodes == 0)
return;
- nodes = palloc(sizeof(RelFileNode) * nnodes); /* non-local relations */
+ nodes = palloc(sizeof(RelFileNodeBackend) * nnodes); /* non-local relations */
/* If it's a local relation, it's localbuf.c's problem. */
for (i = 0; i < nnodes; i++)
{
- if (RelFileNodeBackendIsTemp(rnodes[i]))
+ if (RelFileNodeBackendIsLocalTemp(rnodes[i]))
{
if (rnodes[i].backend == MyBackendId)
DropRelFileNodeAllLocalBuffers(rnodes[i].node);
}
else
- nodes[n++] = rnodes[i].node;
+ nodes[n++] = rnodes[i];
}
/*
@@ -3024,11 +3026,11 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
/* sort the list of rnodes if necessary */
if (use_bsearch)
- pg_qsort(nodes, n, sizeof(RelFileNode), rnode_comparator);
+ pg_qsort(nodes, n, sizeof(RelFileNodeBackend), rnode_comparator);
for (i = 0; i < NBuffers; i++)
{
- RelFileNode *rnode = NULL;
+ RelFileNodeBackend *rnode = NULL;
BufferDesc *bufHdr = GetBufferDescriptor(i);
uint32 buf_state;
@@ -3043,7 +3045,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
for (j = 0; j < n; j++)
{
- if (RelFileNodeEquals(bufHdr->tag.rnode, nodes[j]))
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, nodes[j]))
{
rnode = &nodes[j];
break;
@@ -3053,7 +3055,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
else
{
rnode = bsearch((const void *) &(bufHdr->tag.rnode),
- nodes, n, sizeof(RelFileNode),
+ nodes, n, sizeof(RelFileNodeBackend),
rnode_comparator);
}
@@ -3062,7 +3064,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
continue;
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, (*rnode)))
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, (*rnode)))
InvalidateBuffer(bufHdr); /* releases spinlock */
else
UnlockBufHdr(bufHdr, buf_state);
@@ -3101,11 +3103,11 @@ DropDatabaseBuffers(Oid dbid)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (bufHdr->tag.rnode.dbNode != dbid)
+ if (bufHdr->tag.rnode.node.dbNode != dbid)
continue;
buf_state = LockBufHdr(bufHdr);
- if (bufHdr->tag.rnode.dbNode == dbid)
+ if (bufHdr->tag.rnode.node.dbNode == dbid)
InvalidateBuffer(bufHdr); /* releases spinlock */
else
UnlockBufHdr(bufHdr, buf_state);
@@ -3135,7 +3137,7 @@ PrintBufferDescs(void)
"[%02d] (freeNext=%d, rel=%s, "
"blockNum=%u, flags=0x%x, refcount=%u %d)",
i, buf->freeNext,
- relpathbackend(buf->tag.rnode, InvalidBackendId, buf->tag.forkNum),
+ relpath(buf->tag.rnode, buf->tag.forkNum),
buf->tag.blockNum, buf->flags,
buf->refcount, GetPrivateRefCount(b));
}
@@ -3203,7 +3205,8 @@ FlushRelationBuffers(Relation rel)
uint32 buf_state;
bufHdr = GetLocalBufferDescriptor(i);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+ if (RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node) &&
+ bufHdr->tag.rnode.backend == rel->rd_backend &&
((buf_state = pg_atomic_read_u32(&bufHdr->state)) &
(BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
@@ -3250,13 +3253,15 @@ FlushRelationBuffers(Relation rel)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
+ if (!RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node)
+ || bufHdr->tag.rnode.backend != rel->rd_backend)
continue;
ReservePrivateRefCountEntry();
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+ if (RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node) &&
+ bufHdr->tag.rnode.backend == rel->rd_backend &&
(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
PinBuffer_Locked(bufHdr);
@@ -3304,13 +3309,13 @@ FlushDatabaseBuffers(Oid dbid)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (bufHdr->tag.rnode.dbNode != dbid)
+ if (bufHdr->tag.rnode.node.dbNode != dbid)
continue;
ReservePrivateRefCountEntry();
buf_state = LockBufHdr(bufHdr);
- if (bufHdr->tag.rnode.dbNode == dbid &&
+ if (bufHdr->tag.rnode.node.dbNode == dbid &&
(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
PinBuffer_Locked(bufHdr);
@@ -4050,7 +4055,7 @@ AbortBufferIO(void)
/* Buffer is pinned, so we can read tag without spinlock */
char *path;
- path = relpathperm(buf->tag.rnode, buf->tag.forkNum);
+ path = relpath(buf->tag.rnode, buf->tag.forkNum);
ereport(WARNING,
(errcode(ERRCODE_IO_ERROR),
errmsg("could not write block %u of %s",
@@ -4074,7 +4079,7 @@ shared_buffer_write_error_callback(void *arg)
/* Buffer is pinned, so we can read the tag without locking the spinlock */
if (bufHdr != NULL)
{
- char *path = relpathperm(bufHdr->tag.rnode, bufHdr->tag.forkNum);
+ char *path = relpath(bufHdr->tag.rnode, bufHdr->tag.forkNum);
errcontext("writing block %u of relation %s",
bufHdr->tag.blockNum, path);
@@ -4092,7 +4097,7 @@ local_buffer_write_error_callback(void *arg)
if (bufHdr != NULL)
{
- char *path = relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ char *path = relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum);
errcontext("writing block %u of relation %s",
@@ -4107,22 +4112,27 @@ local_buffer_write_error_callback(void *arg)
static int
rnode_comparator(const void *p1, const void *p2)
{
- RelFileNode n1 = *(const RelFileNode *) p1;
- RelFileNode n2 = *(const RelFileNode *) p2;
+ RelFileNodeBackend n1 = *(const RelFileNodeBackend *) p1;
+ RelFileNodeBackend n2 = *(const RelFileNodeBackend *) p2;
- if (n1.relNode < n2.relNode)
+ if (n1.node.relNode < n2.node.relNode)
return -1;
- else if (n1.relNode > n2.relNode)
+ else if (n1.node.relNode > n2.node.relNode)
return 1;
- if (n1.dbNode < n2.dbNode)
+ if (n1.node.dbNode < n2.node.dbNode)
return -1;
- else if (n1.dbNode > n2.dbNode)
+ else if (n1.node.dbNode > n2.node.dbNode)
return 1;
- if (n1.spcNode < n2.spcNode)
+ if (n1.node.spcNode < n2.node.spcNode)
return -1;
- else if (n1.spcNode > n2.spcNode)
+ else if (n1.node.spcNode > n2.node.spcNode)
+ return 1;
+
+ if (n1.backend < n2.backend)
+ return -1;
+ else if (n1.backend > n2.backend)
return 1;
else
return 0;
@@ -4358,7 +4368,7 @@ IssuePendingWritebacks(WritebackContext *context)
next = &context->pending_writebacks[i + ahead + 1];
/* different file, stop */
- if (!RelFileNodeEquals(cur->tag.rnode, next->tag.rnode) ||
+ if (!RelFileNodeBackendEquals(cur->tag.rnode, next->tag.rnode) ||
cur->tag.forkNum != next->tag.forkNum)
break;
@@ -4377,7 +4387,7 @@ IssuePendingWritebacks(WritebackContext *context)
i += ahead;
/* and finally tell the kernel to write the data to storage */
- reln = smgropen(tag.rnode, InvalidBackendId);
+ reln = smgropen(tag.rnode.node, tag.rnode.backend);
smgrwriteback(reln, tag.forkNum, tag.blockNum, nblocks);
}
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index c462ea8..d197c46 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -68,7 +68,7 @@ LocalPrefetchBuffer(SMgrRelation smgr, ForkNumber forkNum,
BufferTag newTag; /* identity of requested block */
LocalBufferLookupEnt *hresult;
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* Initialize local buffers if first request in this session */
if (LocalBufHash == NULL)
@@ -111,7 +111,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
bool found;
uint32 buf_state;
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* Initialize local buffers if first request in this session */
if (LocalBufHash == NULL)
@@ -209,7 +209,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
Page localpage = (char *) LocalBufHdrGetBlock(bufHdr);
/* Find smgr relation for buffer */
- oreln = smgropen(bufHdr->tag.rnode, MyBackendId);
+ oreln = smgropen(bufHdr->tag.rnode.node, MyBackendId);
PageSetChecksumInplace(localpage, bufHdr->tag.blockNum);
@@ -331,14 +331,14 @@ DropRelFileNodeLocalBuffers(RelFileNode rnode, ForkNumber forkNum,
buf_state = pg_atomic_read_u32(&bufHdr->state);
if ((buf_state & BM_TAG_VALID) &&
- RelFileNodeEquals(bufHdr->tag.rnode, rnode) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, rnode) &&
bufHdr->tag.forkNum == forkNum &&
bufHdr->tag.blockNum >= firstDelBlock)
{
if (LocalRefCount[i] != 0)
elog(ERROR, "block %u of %s is still referenced (local %u)",
bufHdr->tag.blockNum,
- relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum),
LocalRefCount[i]);
/* Remove entry from hashtable */
@@ -377,12 +377,12 @@ DropRelFileNodeAllLocalBuffers(RelFileNode rnode)
buf_state = pg_atomic_read_u32(&bufHdr->state);
if ((buf_state & BM_TAG_VALID) &&
- RelFileNodeEquals(bufHdr->tag.rnode, rnode))
+ RelFileNodeEquals(bufHdr->tag.rnode.node, rnode))
{
if (LocalRefCount[i] != 0)
elog(ERROR, "block %u of %s is still referenced (local %u)",
bufHdr->tag.blockNum,
- relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum),
LocalRefCount[i]);
/* Remove entry from hashtable */
diff --git a/src/backend/storage/freespace/fsmpage.c b/src/backend/storage/freespace/fsmpage.c
index cf7f03f..65eb422 100644
--- a/src/backend/storage/freespace/fsmpage.c
+++ b/src/backend/storage/freespace/fsmpage.c
@@ -268,13 +268,13 @@ restart:
*
* Fix the corruption and restart.
*/
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forknum;
BlockNumber blknum;
BufferGetTag(buf, &rnode, &forknum, &blknum);
elog(DEBUG1, "fixing corrupt FSM block %u, relation %u/%u/%u",
- blknum, rnode.spcNode, rnode.dbNode, rnode.relNode);
+ blknum, rnode.node.spcNode, rnode.node.dbNode, rnode.node.relNode);
/* make sure we hold an exclusive lock */
if (!exclusive_lock_held)
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index bbcd18d..f4dbd0a 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -32,6 +32,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"
@@ -86,6 +87,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) \
@@ -151,6 +164,48 @@ 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)
+ {
+ /* Remove relation pages from shared buffers */
+ DropRelFileNodesAllBuffers(&rel->rnode, 1);
+
+ /* 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?
*
@@ -204,6 +259,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);
@@ -451,6 +508,19 @@ mdopen(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))
{
@@ -462,6 +532,7 @@ mdopen(SMgrRelation reln, ForkNumber forknum, int behavior)
errmsg("could not open file \"%s\": %m", path)));
}
+ NewSegment:
pfree(path);
_fdvec_resize(reln, forknum, 1);
@@ -627,8 +698,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),
@@ -713,12 +789,18 @@ mdwrite(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
BlockNumber
mdnblocks(SMgrRelation reln, ForkNumber forknum)
{
- MdfdVec *v = mdopen(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 = mdopen(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 2b992d7..b9fbf01 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1099,6 +1099,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;
@@ -3295,6 +3299,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 9f59cc7..159321f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15582,8 +15582,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/buf_internals.h b/src/include/storage/buf_internals.h
index df2dda7..7adb96b 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -90,16 +90,17 @@
*/
typedef struct buftag
{
- RelFileNode rnode; /* physical relation identifier */
+ RelFileNodeBackend rnode; /* physical relation identifier */
ForkNumber forkNum;
BlockNumber blockNum; /* blknum relative to begin of reln */
} BufferTag;
#define CLEAR_BUFFERTAG(a) \
( \
- (a).rnode.spcNode = InvalidOid, \
- (a).rnode.dbNode = InvalidOid, \
- (a).rnode.relNode = InvalidOid, \
+ (a).rnode.node.spcNode = InvalidOid, \
+ (a).rnode.node.dbNode = InvalidOid, \
+ (a).rnode.node.relNode = InvalidOid, \
+ (a).rnode.backend = InvalidBackendId, \
(a).forkNum = InvalidForkNumber, \
(a).blockNum = InvalidBlockNumber \
)
@@ -113,7 +114,7 @@ typedef struct buftag
#define BUFFERTAGS_EQUAL(a,b) \
( \
- RelFileNodeEquals((a).rnode, (b).rnode) && \
+ RelFileNodeBackendEquals((a).rnode, (b).rnode) && \
(a).blockNum == (b).blockNum && \
(a).forkNum == (b).forkNum \
)
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 509f4b7..3315fa0 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -205,7 +205,7 @@ extern XLogRecPtr BufferGetLSNAtomic(Buffer buffer);
extern void PrintPinnedBufs(void);
#endif
extern Size BufferShmemSize(void);
-extern void BufferGetTag(Buffer buffer, RelFileNode *rnode,
+extern void BufferGetTag(Buffer buffer, RelFileNodeBackend *rnode,
ForkNumber *forknum, BlockNumber *blknum);
extern void MarkBufferDirtyHint(Buffer buffer, bool buffer_std);
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/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 f23fe8d..13da565 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -106,7 +106,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 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 ca200eb..f9b17e0 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -171,6 +171,7 @@ test: limit
test: plpgsql
test: copy2
test: temp
+test: session_table
test: domain
test: rangefuncs
test: prepare
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;
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-01 03:10 Craig Ringer <craig@2ndquadrant.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Craig Ringer @ 2019-08-01 03:10 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: pgsql-hackers
On Wed, 31 Jul 2019 at 23:05, Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
wrote:
> Current Postgres implementation of temporary table causes number of
> problems:
>
> 1. Catalog bloating: if client creates and deletes too many temporary
> tables, then autovacuum get stuck on catalog.
>
This also upsets logical decoding a little - AFAICS it still has to treat
transactions that use temporary tables as catalog-modifying transactions,
tracking them in its historic catalog snapshots and doing extra cache
flushes etc when decoding them.
This will become even more important as we work to support eager/optimistic
output plugin processing of in-progress transactions. We'd have to switch
snapshots more, and that can get quite expensive so using temp tables could
really hurt performance. Or we'd have to serialize on catalog-changing
transactions, in which case using temp tables would negate the benefits of
optimistic streaming of in-progress transactions.
> 3. It is not possible to use temporary tables at replica.
For physical replicas, yes.
> Hot standby
> configuration is frequently used to run OLAP queries on replica
> and results of such queries are used to be saved in temporary tables.
> Right now it is not possible (except "hackers" solution with storing
> results in file_fdw).
>
Right. Because we cannot modify pg_class, pg_attribute etc, even though we
could reasonably enough write to local-only relfilenodes on a replica if we
didn't have to change WAL-logged catalog tables.
I've seen some hacks suggested around this where we have an unlogged fork
of each of the needed catalog tables, allowing replicas to write temp table
info to them. We'd scan both the logged and unlogged forks when doing
relcache management etc. But there are plenty of ugly issues with this.
We'd have to reserve oid ranges for them which is ugly; to make it BC
friendly those reservations would probably have to take the form of some
kind of placeholder entry in the real pg_class. And it gets ickier from
there. It hardly seems worth it when we should probably just implement
global temp tables instead.
> 5. Inefficient memory usage and possible memory overflow: each backend
> maintains its own local buffers for work with temporary tables.
>
Is there any reason that would change with global temp tables? We'd still
be creating a backend-local relfilenode for each backend that actually
writes to the temp table, and I don't see how it'd be useful or practical
to keep those in shared_buffers.
Using local buffers has big advantages too. It saves shared_buffers space
for data where there's actually some possibility of getting cache hits, or
for where we can benefit from lazy/async writeback and write combining. I
wouldn't want to keep temp data there if I had the option.
If you're concerned about the memory use of backend local temp buffers, or
about how we account for and limit those, that's worth looking into. But I
don't think it'd be something that should be affected by global-temp vs
backend-local-temp tables.
> Default size of temporary buffers is 8Mb. It seems to be too small for
> modern servers having hundreds of gigabytes of RAM, causing extra
> copying of data between OS cache and local buffers. But if there are
thousands of backends, each executing queries with temporary tables,
then total amount of memory used for temporary buffers can exceed
several tens of gigabytes.
>
Right. But what solution do you propose for this? Putting that in
shared_buffers will do nothing except deprive shared_buffers of space that
can be used for other more useful things. A server-wide temp buffer would
add IPC and locking overheads and AFAICS little benefit. One of the big
appeals of temp tables is that we don't need any of that.
If you want to improve server-wide temp buffer memory accounting and
management that makes sense. I can see it being useful to have things like
a server-wide DSM/DSA pool of temp buffers that backends borrow from and
return to based on memory pressure on a LRU-ish basis, maybe. But I can
also see how that'd be complex and hard to get right. It'd also be prone to
priority inversion problems where an idle/inactive backend must be woken up
to release memory or release locks, depriving an actively executing backend
of runtime. And it'd be as likely to create inefficiencies with copying and
eviction as solve them since backends could easily land up taking turns
kicking each other out of memory and re-reading their own data.
I don't think this is something that should be tackled as part of work on
global temp tables personally.
> 6. Connection pooler can not reschedule session which has created
> temporary tables to some other backend because it's data is stored in local
> buffers.
>
Yeah, if you're using transaction-associative pooling. That's just part of
a more general problem though, there are piles of related issues with temp
tables, session GUCs, session advisory locks and more.
I don't see how global temp tables will do you the slightest bit of good
here as the data in them will still be backend-local. If it isn't then you
should just be using unlogged tables.
> Definition of this table (metadata) is shared by all backends but data
> is private to the backend. After session termination data is obviously
> lost.
>
+1 that's what a global temp table should be, and it's IIRC pretty much how
the SQL standard specifies temp tables.
I suspect I'm overlooking some complexities here, because to me it seems
like we could implement these fairly simply. A new relkind would identify
it as a global temp table and the relfilenode would be 0. Same for indexes
on temp tables. We'd extend the relfilenode mapper to support a
backend-local non-persistent relfilenode map that's used to track temp
table and index relfilenodes. If no relfilenode is defined for the table,
the mapper would allocate one. We already happily create missing
relfilenodes on write so we don't even have to pre-create the actual file.
We'd register the relfilenode as a tempfile and use existing tempfile
cleanup mechanisms, and we'd use the temp tablespace to store it.
I must be missing something important because it doesn't seem hard.
Global temporary tables are accessed though shared buffers (to solve
> problem 2).
>
I'm far from convinced of the wisdom or necessity of that, but I haven't
spent as much time digging into this problem as you have.
> The drawback of such approach is that it will be necessary to
> reimplement large bulk of heapam code.
> But this approach allows to eliminate visibility check for temporary
> table tuples and decrease size of tuple header.
>
That sounds potentially cool, but perhaps a "next step" thing? Allow the
creation of global temp tables to specify reloptions, and you can add it as
a reloption later. You can't actually eliminate visibility checks anyway
because they're still MVCC heaps. Savepoints can create invisible tuples
even if you're using temp tables that are cleared on commit, and of course
so can DELETEs or UPDATEs. So I'm not sure how much use it'd really be in
practice.
--
Craig Ringer http://www.2ndQuadrant.com/
2ndQuadrant - PostgreSQL Solutions for the Enterprise
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-01 08:13 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Craig Ringer <craig@2ndquadrant.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-08-01 08:13 UTC (permalink / raw)
To: Craig Ringer <craig@2ndquadrant.com>; +Cc: pgsql-hackers
On 01.08.2019 6:10, Craig Ringer wrote:
>
> 3. It is not possible to use temporary tables at replica.
>
>
> For physical replicas, yes.
Yes, definitely logical replicas (for example our PgPro-EE multimaster
based on logical replication) do not suffer from this problem.
But in case of multimaster we have another problem related with
temporary tables: we have to use 2PC for each transaction and using
temporary tables in prepared transaction is now prohibited.
This was the motivation of the patch proposed by Stas Kelvich which
allows to use temporary tables in prepared transactions under some
conditions.
> 5. Inefficient memory usage and possible memory overflow: each backend
>
> maintains its own local buffers for work with temporary tables.
>
>
> Is there any reason that would change with global temp tables? We'd
> still be creating a backend-local relfilenode for each backend that
> actually writes to the temp table, and I don't see how it'd be useful
> or practical to keep those in shared_buffers.
>
Yes, my implementation of global temp tables is using shared buffers.
It was not strictly needed as far as data is local. It is possible to
have shared metadata and private data accessed through local buffers.
But I have done it for three reasons:
1, Make it possible to use parallel plans for temp tables.
2. Eliminate memory overflow problem.
3. Make in possible to reschedule session to other backens (connection
pooler).
> Using local buffers has big advantages too. It saves shared_buffers
> space for data where there's actually some possibility of getting
> cache hits, or for where we can benefit from lazy/async writeback and
> write combining. I wouldn't want to keep temp data there if I had the
> option.
Definitely local buffers have some advantages:
- do not require synchronization
- avoid flushing data from shared buffers
But global temp tables are not excluding use of original (local) temp
tables.
So you will have a choice: either to use local temp tables which can be
easily created on demand and accessed through local buffers,
either create global temp tables, which eliminate catalog bloating,
allow parallel queries and which data is controlled by the same cache
replacement discipline as for normal tables...
>
>
> Default size of temporary buffers is 8Mb. It seems to be too small
> for
> modern servers having hundreds of gigabytes of RAM, causing extra
> copying of data between OS cache and local buffers. But if there are
>
> thousands of backends, each executing queries with temporary tables,
>
> then total amount of memory used for temporary buffers can exceed
>
> several tens of gigabytes.
>
>
> Right. But what solution do you propose for this? Putting that in
> shared_buffers will do nothing except deprive shared_buffers of space
> that can be used for other more useful things. A server-wide temp
> buffer would add IPC and locking overheads and AFAICS little benefit.
> One of the big appeals of temp tables is that we don't need any of that.
I do not think that parallel execution and efficient connection pooling
are "little benefit".
>
> If you want to improve server-wide temp buffer memory accounting and
> management that makes sense. I can see it being useful to have things
> like a server-wide DSM/DSA pool of temp buffers that backends borrow
> from and return to based on memory pressure on a LRU-ish basis, maybe.
> But I can also see how that'd be complex and hard to get right. It'd
> also be prone to priority inversion problems where an idle/inactive
> backend must be woken up to release memory or release locks, depriving
> an actively executing backend of runtime. And it'd be as likely to
> create inefficiencies with copying and eviction as solve them since
> backends could easily land up taking turns kicking each other out of
> memory and re-reading their own data.
>
> I don't think this is something that should be tackled as part of work
> on global temp tables personally.
My assumptions are the following: temporary tables are mostly used in
OLAP queries. And OLAP workload means that there are few concurrent
queries which are working with large datasets.
So size of produced temporary tables can be quite big. For OLAP it seems
to be very important to be able to use parallel query execution and use
the same cache eviction rule both for persistent and temp tables
(otherwise you either cause swapping, either extra copying of data
between OS and Postgres caches).
>
> 6. Connection pooler can not reschedule session which has created
> temporary tables to some other backend because it's data is stored
> in local buffers.
>
>
> Yeah, if you're using transaction-associative pooling. That's just
> part of a more general problem though, there are piles of related
> issues with temp tables, session GUCs, session advisory locks and more.
>
> I don't see how global temp tables will do you the slightest bit of
> good here as the data in them will still be backend-local. If it isn't
> then you should just be using unlogged tables.
You can not use the same unlogged table to save intermediate query
results in two parallel sessions.
> Definition of this table (metadata) is shared by all backends but
> data
> is private to the backend. After session termination data is
> obviously lost.
>
>
> +1 that's what a global temp table should be, and it's IIRC pretty
> much how the SQL standard specifies temp tables.
>
> I suspect I'm overlooking some complexities here, because to me it
> seems like we could implement these fairly simply. A new relkind would
> identify it as a global temp table and the relfilenode would be 0.
> Same for indexes on temp tables. We'd extend the relfilenode mapper to
> support a backend-local non-persistent relfilenode map that's used to
> track temp table and index relfilenodes. If no relfilenode is defined
> for the table, the mapper would allocate one. We already happily
> create missing relfilenodes on write so we don't even have to
> pre-create the actual file. We'd register the relfilenode as a
> tempfile and use existing tempfile cleanup mechanisms, and we'd use
> the temp tablespace to store it.
>
> I must be missing something important because it doesn't seem hard.
>
As I already wrote, I tried to kill two bird with one stone: eliminate
catalog bloating and allow access to temp tables from multiple backends
(to be able to perform parallel queries and connection pooling).
This is why I have to use shared buffers for global temp tables.
May be it was not so good idea. But it was one of my primary intention
of publishing this patch to know opinion of other people.
In PG-Pro some of my colleagues think that the most critical problem is
inability to use temporary tables at replica.
Other think that it is not a problem at all if you are using logical
replication.
From my point of view the most critical problem is inability to use
parallel plans for temporary tables.
But looks like you don't think so.
I see three different activities related with temporary tables:
1. Shared metadata
2. Shared buffers
3. Alternative concurrency control & reducing tuple header size
(specialized table access method for temporary tables)
In my proposal I combined 1 and 2, leaving 3 for next step.
I will be interested to know other suggestions.
One more thing - 1 and 2 are really independent: you can share metadata
without sharing buffers.
But introducing yet another kind of temporary tables seems to be really
overkill:
- local temp tables (private namespace and lcoal buffers)
- tables with shared metadata but local bufferes
- tables with shared metadata and bufferes
> The drawback of such approach is that it will be necessary to
> reimplement large bulk of heapam code.
> But this approach allows to eliminate visibility check for temporary
> table tuples and decrease size of tuple header.
>
>
> That sounds potentially cool, but perhaps a "next step" thing? Allow
> the creation of global temp tables to specify reloptions, and you can
> add it as a reloption later. You can't actually eliminate visibility
> checks anyway because they're still MVCC heaps.
Sorry?
I mean elimination of MVCC overhead (visibility checks) for temp tables
only.
I am not sure that we can really fully eliminate it if we support use of
temp tables in prepared transactions and autonomous transactions (yet
another awful feature we have in PgPro-EE).
Also looks like we need to have some analogue of CID to be able to
correctly executed queries like "insert into T (select from T ...)"
where T is global temp table.
I didn't think much about it, but I really considering new table access
method API for reducing per-tuple storage overhead for temporary and
append-only tables.
> Savepoints can create invisible tuples even if you're using temp
> tables that are cleared on commit, and of course so can DELETEs or
> UPDATEs. So I'm not sure how much use it'd really be in practice.
>
Yehh, subtransactions can be also a problem for eliminating xmin/xmax
for temp tables. Thanks for noticing it.
I noticed that I have not patched some extension - fixed and rebased
version of the patch is attached.
Also you can find this version in our github repository:
https://github.com/postgrespro/postgresql.builtin_pool.git
branch global_temp.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
[text/x-patch] session_tables-1.patch (42.7K, ../../0baf9e02-23b3-f3c1-6fe9-fd35aee6fee1@postgrespro.ru/3-session_tables-1.patch)
download | inline diff:
diff --git a/contrib/pg_buffercache/pg_buffercache_pages.c b/contrib/pg_buffercache/pg_buffercache_pages.c
index 1bd579f..2d93f6f 100644
--- a/contrib/pg_buffercache/pg_buffercache_pages.c
+++ b/contrib/pg_buffercache/pg_buffercache_pages.c
@@ -153,9 +153,9 @@ pg_buffercache_pages(PG_FUNCTION_ARGS)
buf_state = LockBufHdr(bufHdr);
fctx->record[i].bufferid = BufferDescriptorGetBuffer(bufHdr);
- fctx->record[i].relfilenode = bufHdr->tag.rnode.relNode;
- fctx->record[i].reltablespace = bufHdr->tag.rnode.spcNode;
- fctx->record[i].reldatabase = bufHdr->tag.rnode.dbNode;
+ fctx->record[i].relfilenode = bufHdr->tag.rnode.node.relNode;
+ fctx->record[i].reltablespace = bufHdr->tag.rnode.node.spcNode;
+ fctx->record[i].reldatabase = bufHdr->tag.rnode.node.dbNode;
fctx->record[i].forknum = bufHdr->tag.forkNum;
fctx->record[i].blocknum = bufHdr->tag.blockNum;
fctx->record[i].usagecount = BUF_STATE_GET_USAGECOUNT(buf_state);
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 38ae240..8a04954 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -608,9 +608,9 @@ apw_dump_now(bool is_bgworker, bool dump_unlogged)
if (buf_state & BM_TAG_VALID &&
((buf_state & BM_PERMANENT) || dump_unlogged))
{
- block_info_array[num_blocks].database = bufHdr->tag.rnode.dbNode;
- block_info_array[num_blocks].tablespace = bufHdr->tag.rnode.spcNode;
- block_info_array[num_blocks].filenode = bufHdr->tag.rnode.relNode;
+ block_info_array[num_blocks].database = bufHdr->tag.rnode.node.dbNode;
+ block_info_array[num_blocks].tablespace = bufHdr->tag.rnode.node.spcNode;
+ block_info_array[num_blocks].filenode = bufHdr->tag.rnode.node.relNode;
block_info_array[num_blocks].forknum = bufHdr->tag.forkNum;
block_info_array[num_blocks].blocknum = bufHdr->tag.blockNum;
++num_blocks;
diff --git a/src/backend/access/gin/ginxlog.c b/src/backend/access/gin/ginxlog.c
index c945b28..14d4e48 100644
--- a/src/backend/access/gin/ginxlog.c
+++ b/src/backend/access/gin/ginxlog.c
@@ -95,13 +95,13 @@ ginRedoInsertEntry(Buffer buffer, bool isLeaf, BlockNumber rightblkno, void *rda
if (PageAddItem(page, (Item) itup, IndexTupleSize(itup), offset, false, false) == InvalidOffsetNumber)
{
- RelFileNode node;
+ RelFileNodeBackend rnode;
ForkNumber forknum;
BlockNumber blknum;
- BufferGetTag(buffer, &node, &forknum, &blknum);
+ BufferGetTag(buffer, &rnode, &forknum, &blknum);
elog(ERROR, "failed to add item to index page in %u/%u/%u",
- node.spcNode, node.dbNode, node.relNode);
+ rnode.node.spcNode, rnode.node.dbNode, rnode.node.relNode);
}
}
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 09bc6fe..c60effd 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -671,6 +671,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 5962126..60f4696 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 && PageIsNew(BufferGetPage(buf)) && IsSessionRelationBackendId(rel->rd_backend))
+ _bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
+ else
+ _bt_checkpage(rel, buf);
}
else
{
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index 3ec67d4..edec8ca 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -213,6 +213,7 @@ void
XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
{
registered_buffer *regbuf;
+ RelFileNodeBackend rnode;
/* NO_IMAGE doesn't make sense with FORCE_IMAGE */
Assert(!((flags & REGBUF_FORCE_IMAGE) && (flags & (REGBUF_NO_IMAGE))));
@@ -227,7 +228,8 @@ XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
regbuf = ®istered_buffers[block_id];
- BufferGetTag(buffer, ®buf->rnode, ®buf->forkno, ®buf->block);
+ BufferGetTag(buffer, &rnode, ®buf->forkno, ®buf->block);
+ regbuf->rnode = rnode.node;
regbuf->page = BufferGetPage(buffer);
regbuf->flags = flags;
regbuf->rdata_tail = (XLogRecData *) ®buf->rdata_head;
@@ -919,7 +921,7 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std)
int flags;
PGAlignedBlock copied_buffer;
char *origdata = (char *) BufferGetBlock(buffer);
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forkno;
BlockNumber blkno;
@@ -948,7 +950,7 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std)
flags |= REGBUF_STANDARD;
BufferGetTag(buffer, &rnode, &forkno, &blkno);
- XLogRegisterBlock(0, &rnode, forkno, blkno, copied_buffer.data, flags);
+ XLogRegisterBlock(0, &rnode.node, forkno, blkno, copied_buffer.data, flags);
recptr = XLogInsert(RM_XLOG_ID, XLOG_FPI_FOR_HINT);
}
@@ -1009,7 +1011,7 @@ XLogRecPtr
log_newpage_buffer(Buffer buffer, bool page_std)
{
Page page = BufferGetPage(buffer);
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forkNum;
BlockNumber blkno;
@@ -1018,7 +1020,7 @@ log_newpage_buffer(Buffer buffer, bool page_std)
BufferGetTag(buffer, &rnode, &forkNum, &blkno);
- return log_newpage(&rnode, forkNum, blkno, page, page_std);
+ return log_newpage(&rnode.node, forkNum, blkno, page, page_std);
}
/*
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index a065419..8814afb 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -409,6 +409,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 99ae159..24b2438 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3612,7 +3612,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 3cc886f..a111ddc 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 cedb4ee..d11c5b3 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/tablecmds.c b/src/backend/commands/tablecmds.c
index fb2be10..372c9a5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -7678,6 +7678,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,
@@ -14082,6 +14088,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 */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c97bb36..f9b2000 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3265,20 +3265,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/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index e8ffa04..2004d2f 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -3483,6 +3483,7 @@ ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
{
ReorderBufferTupleCidKey key;
ReorderBufferTupleCidEnt *ent;
+ RelFileNodeBackend rnode;
ForkNumber forkno;
BlockNumber blockno;
bool updated_mapping = false;
@@ -3496,7 +3497,8 @@ ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
* get relfilenode from the buffer, no convenient way to access it other
* than that.
*/
- BufferGetTag(buffer, &key.relnode, &forkno, &blockno);
+ BufferGetTag(buffer, &rnode, &forkno, &blockno);
+ key.relnode = rnode.node;
/* tuples can only be in the main fork */
Assert(forkno == MAIN_FORKNUM);
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 6f3a402..76ce953 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -556,7 +556,7 @@ PrefetchBuffer(Relation reln, ForkNumber forkNum, BlockNumber blockNum)
int buf_id;
/* create a tag so we can lookup the buffer */
- INIT_BUFFERTAG(newTag, reln->rd_smgr->smgr_rnode.node,
+ INIT_BUFFERTAG(newTag, reln->rd_smgr->smgr_rnode,
forkNum, blockNum);
/* determine its hash code and partition lock ID */
@@ -710,7 +710,7 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
Block bufBlock;
bool found;
bool isExtend;
- bool isLocalBuf = SmgrIsTemp(smgr);
+ bool isLocalBuf = SmgrIsTemp(smgr) && relpersistence == RELPERSISTENCE_TEMP;
*hit = false;
@@ -1010,7 +1010,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
uint32 buf_state;
/* create a tag so we can lookup the buffer */
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* determine its hash code and partition lock ID */
newHash = BufTableHashCode(&newTag);
@@ -1532,7 +1532,8 @@ ReleaseAndReadBuffer(Buffer buffer,
{
bufHdr = GetLocalBufferDescriptor(-buffer - 1);
if (bufHdr->tag.blockNum == blockNum &&
- RelFileNodeEquals(bufHdr->tag.rnode, relation->rd_node) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, relation->rd_node) &&
+ bufHdr->tag.rnode.backend == relation->rd_backend &&
bufHdr->tag.forkNum == forkNum)
return buffer;
ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
@@ -1543,7 +1544,8 @@ ReleaseAndReadBuffer(Buffer buffer,
bufHdr = GetBufferDescriptor(buffer - 1);
/* we have pin, so it's ok to examine tag without spinlock */
if (bufHdr->tag.blockNum == blockNum &&
- RelFileNodeEquals(bufHdr->tag.rnode, relation->rd_node) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, relation->rd_node) &&
+ bufHdr->tag.rnode.backend == relation->rd_backend &&
bufHdr->tag.forkNum == forkNum)
return buffer;
UnpinBuffer(bufHdr, true);
@@ -1845,8 +1847,8 @@ BufferSync(int flags)
item = &CkptBufferIds[num_to_scan++];
item->buf_id = buf_id;
- item->tsId = bufHdr->tag.rnode.spcNode;
- item->relNode = bufHdr->tag.rnode.relNode;
+ item->tsId = bufHdr->tag.rnode.node.spcNode;
+ item->relNode = bufHdr->tag.rnode.node.relNode;
item->forkNum = bufHdr->tag.forkNum;
item->blockNum = bufHdr->tag.blockNum;
}
@@ -2559,7 +2561,7 @@ PrintBufferLeakWarning(Buffer buffer)
}
/* theoretically we should lock the bufhdr here */
- path = relpathbackend(buf->tag.rnode, backend, buf->tag.forkNum);
+ path = relpathbackend(buf->tag.rnode.node, backend, buf->tag.forkNum);
buf_state = pg_atomic_read_u32(&buf->state);
elog(WARNING,
"buffer refcount leak: [%03d] "
@@ -2631,7 +2633,7 @@ BufferGetBlockNumber(Buffer buffer)
* a buffer.
*/
void
-BufferGetTag(Buffer buffer, RelFileNode *rnode, ForkNumber *forknum,
+BufferGetTag(Buffer buffer, RelFileNodeBackend *rnode, ForkNumber *forknum,
BlockNumber *blknum)
{
BufferDesc *bufHdr;
@@ -2696,7 +2698,7 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
/* Find smgr relation for buffer */
if (reln == NULL)
- reln = smgropen(buf->tag.rnode, InvalidBackendId);
+ reln = smgropen(buf->tag.rnode.node, buf->tag.rnode.backend);
TRACE_POSTGRESQL_BUFFER_FLUSH_START(buf->tag.forkNum,
buf->tag.blockNum,
@@ -2930,7 +2932,7 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber forkNum,
int i;
/* If it's a local relation, it's localbuf.c's problem. */
- if (RelFileNodeBackendIsTemp(rnode))
+ if (RelFileNodeBackendIsLocalTemp(rnode))
{
if (rnode.backend == MyBackendId)
DropRelFileNodeLocalBuffers(rnode.node, forkNum, firstDelBlock);
@@ -2958,11 +2960,11 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber forkNum,
* We could check forkNum and blockNum as well as the rnode, but the
* incremental win from doing so seems small.
*/
- if (!RelFileNodeEquals(bufHdr->tag.rnode, rnode.node))
+ if (!RelFileNodeBackendEquals(bufHdr->tag.rnode, rnode))
continue;
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rnode.node) &&
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, rnode) &&
bufHdr->tag.forkNum == forkNum &&
bufHdr->tag.blockNum >= firstDelBlock)
InvalidateBuffer(bufHdr); /* releases spinlock */
@@ -2985,24 +2987,24 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
{
int i,
n = 0;
- RelFileNode *nodes;
+ RelFileNodeBackend *nodes;
bool use_bsearch;
if (nnodes == 0)
return;
- nodes = palloc(sizeof(RelFileNode) * nnodes); /* non-local relations */
+ nodes = palloc(sizeof(RelFileNodeBackend) * nnodes); /* non-local relations */
/* If it's a local relation, it's localbuf.c's problem. */
for (i = 0; i < nnodes; i++)
{
- if (RelFileNodeBackendIsTemp(rnodes[i]))
+ if (RelFileNodeBackendIsLocalTemp(rnodes[i]))
{
if (rnodes[i].backend == MyBackendId)
DropRelFileNodeAllLocalBuffers(rnodes[i].node);
}
else
- nodes[n++] = rnodes[i].node;
+ nodes[n++] = rnodes[i];
}
/*
@@ -3025,11 +3027,11 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
/* sort the list of rnodes if necessary */
if (use_bsearch)
- pg_qsort(nodes, n, sizeof(RelFileNode), rnode_comparator);
+ pg_qsort(nodes, n, sizeof(RelFileNodeBackend), rnode_comparator);
for (i = 0; i < NBuffers; i++)
{
- RelFileNode *rnode = NULL;
+ RelFileNodeBackend *rnode = NULL;
BufferDesc *bufHdr = GetBufferDescriptor(i);
uint32 buf_state;
@@ -3044,7 +3046,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
for (j = 0; j < n; j++)
{
- if (RelFileNodeEquals(bufHdr->tag.rnode, nodes[j]))
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, nodes[j]))
{
rnode = &nodes[j];
break;
@@ -3054,7 +3056,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
else
{
rnode = bsearch((const void *) &(bufHdr->tag.rnode),
- nodes, n, sizeof(RelFileNode),
+ nodes, n, sizeof(RelFileNodeBackend),
rnode_comparator);
}
@@ -3063,7 +3065,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
continue;
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, (*rnode)))
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, (*rnode)))
InvalidateBuffer(bufHdr); /* releases spinlock */
else
UnlockBufHdr(bufHdr, buf_state);
@@ -3102,11 +3104,11 @@ DropDatabaseBuffers(Oid dbid)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (bufHdr->tag.rnode.dbNode != dbid)
+ if (bufHdr->tag.rnode.node.dbNode != dbid)
continue;
buf_state = LockBufHdr(bufHdr);
- if (bufHdr->tag.rnode.dbNode == dbid)
+ if (bufHdr->tag.rnode.node.dbNode == dbid)
InvalidateBuffer(bufHdr); /* releases spinlock */
else
UnlockBufHdr(bufHdr, buf_state);
@@ -3136,7 +3138,7 @@ PrintBufferDescs(void)
"[%02d] (freeNext=%d, rel=%s, "
"blockNum=%u, flags=0x%x, refcount=%u %d)",
i, buf->freeNext,
- relpathbackend(buf->tag.rnode, InvalidBackendId, buf->tag.forkNum),
+ relpath(buf->tag.rnode, buf->tag.forkNum),
buf->tag.blockNum, buf->flags,
buf->refcount, GetPrivateRefCount(b));
}
@@ -3204,7 +3206,8 @@ FlushRelationBuffers(Relation rel)
uint32 buf_state;
bufHdr = GetLocalBufferDescriptor(i);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+ if (RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node) &&
+ bufHdr->tag.rnode.backend == rel->rd_backend &&
((buf_state = pg_atomic_read_u32(&bufHdr->state)) &
(BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
@@ -3251,13 +3254,15 @@ FlushRelationBuffers(Relation rel)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
+ if (!RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node)
+ || bufHdr->tag.rnode.backend != rel->rd_backend)
continue;
ReservePrivateRefCountEntry();
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+ if (RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node) &&
+ bufHdr->tag.rnode.backend == rel->rd_backend &&
(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
PinBuffer_Locked(bufHdr);
@@ -3305,13 +3310,13 @@ FlushDatabaseBuffers(Oid dbid)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (bufHdr->tag.rnode.dbNode != dbid)
+ if (bufHdr->tag.rnode.node.dbNode != dbid)
continue;
ReservePrivateRefCountEntry();
buf_state = LockBufHdr(bufHdr);
- if (bufHdr->tag.rnode.dbNode == dbid &&
+ if (bufHdr->tag.rnode.node.dbNode == dbid &&
(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
PinBuffer_Locked(bufHdr);
@@ -4051,7 +4056,7 @@ AbortBufferIO(void)
/* Buffer is pinned, so we can read tag without spinlock */
char *path;
- path = relpathperm(buf->tag.rnode, buf->tag.forkNum);
+ path = relpath(buf->tag.rnode, buf->tag.forkNum);
ereport(WARNING,
(errcode(ERRCODE_IO_ERROR),
errmsg("could not write block %u of %s",
@@ -4075,7 +4080,7 @@ shared_buffer_write_error_callback(void *arg)
/* Buffer is pinned, so we can read the tag without locking the spinlock */
if (bufHdr != NULL)
{
- char *path = relpathperm(bufHdr->tag.rnode, bufHdr->tag.forkNum);
+ char *path = relpath(bufHdr->tag.rnode, bufHdr->tag.forkNum);
errcontext("writing block %u of relation %s",
bufHdr->tag.blockNum, path);
@@ -4093,7 +4098,7 @@ local_buffer_write_error_callback(void *arg)
if (bufHdr != NULL)
{
- char *path = relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ char *path = relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum);
errcontext("writing block %u of relation %s",
@@ -4108,22 +4113,27 @@ local_buffer_write_error_callback(void *arg)
static int
rnode_comparator(const void *p1, const void *p2)
{
- RelFileNode n1 = *(const RelFileNode *) p1;
- RelFileNode n2 = *(const RelFileNode *) p2;
+ RelFileNodeBackend n1 = *(const RelFileNodeBackend *) p1;
+ RelFileNodeBackend n2 = *(const RelFileNodeBackend *) p2;
- if (n1.relNode < n2.relNode)
+ if (n1.node.relNode < n2.node.relNode)
return -1;
- else if (n1.relNode > n2.relNode)
+ else if (n1.node.relNode > n2.node.relNode)
return 1;
- if (n1.dbNode < n2.dbNode)
+ if (n1.node.dbNode < n2.node.dbNode)
return -1;
- else if (n1.dbNode > n2.dbNode)
+ else if (n1.node.dbNode > n2.node.dbNode)
return 1;
- if (n1.spcNode < n2.spcNode)
+ if (n1.node.spcNode < n2.node.spcNode)
return -1;
- else if (n1.spcNode > n2.spcNode)
+ else if (n1.node.spcNode > n2.node.spcNode)
+ return 1;
+
+ if (n1.backend < n2.backend)
+ return -1;
+ else if (n1.backend > n2.backend)
return 1;
else
return 0;
@@ -4359,7 +4369,7 @@ IssuePendingWritebacks(WritebackContext *context)
next = &context->pending_writebacks[i + ahead + 1];
/* different file, stop */
- if (!RelFileNodeEquals(cur->tag.rnode, next->tag.rnode) ||
+ if (!RelFileNodeBackendEquals(cur->tag.rnode, next->tag.rnode) ||
cur->tag.forkNum != next->tag.forkNum)
break;
@@ -4378,7 +4388,7 @@ IssuePendingWritebacks(WritebackContext *context)
i += ahead;
/* and finally tell the kernel to write the data to storage */
- reln = smgropen(tag.rnode, InvalidBackendId);
+ reln = smgropen(tag.rnode.node, tag.rnode.backend);
smgrwriteback(reln, tag.forkNum, tag.blockNum, nblocks);
}
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index f5f6a29..6bd5ecb 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -68,7 +68,7 @@ LocalPrefetchBuffer(SMgrRelation smgr, ForkNumber forkNum,
BufferTag newTag; /* identity of requested block */
LocalBufferLookupEnt *hresult;
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* Initialize local buffers if first request in this session */
if (LocalBufHash == NULL)
@@ -111,7 +111,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
bool found;
uint32 buf_state;
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* Initialize local buffers if first request in this session */
if (LocalBufHash == NULL)
@@ -209,7 +209,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
Page localpage = (char *) LocalBufHdrGetBlock(bufHdr);
/* Find smgr relation for buffer */
- oreln = smgropen(bufHdr->tag.rnode, MyBackendId);
+ oreln = smgropen(bufHdr->tag.rnode.node, MyBackendId);
PageSetChecksumInplace(localpage, bufHdr->tag.blockNum);
@@ -331,14 +331,14 @@ DropRelFileNodeLocalBuffers(RelFileNode rnode, ForkNumber forkNum,
buf_state = pg_atomic_read_u32(&bufHdr->state);
if ((buf_state & BM_TAG_VALID) &&
- RelFileNodeEquals(bufHdr->tag.rnode, rnode) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, rnode) &&
bufHdr->tag.forkNum == forkNum &&
bufHdr->tag.blockNum >= firstDelBlock)
{
if (LocalRefCount[i] != 0)
elog(ERROR, "block %u of %s is still referenced (local %u)",
bufHdr->tag.blockNum,
- relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum),
LocalRefCount[i]);
/* Remove entry from hashtable */
@@ -377,12 +377,12 @@ DropRelFileNodeAllLocalBuffers(RelFileNode rnode)
buf_state = pg_atomic_read_u32(&bufHdr->state);
if ((buf_state & BM_TAG_VALID) &&
- RelFileNodeEquals(bufHdr->tag.rnode, rnode))
+ RelFileNodeEquals(bufHdr->tag.rnode.node, rnode))
{
if (LocalRefCount[i] != 0)
elog(ERROR, "block %u of %s is still referenced (local %u)",
bufHdr->tag.blockNum,
- relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum),
LocalRefCount[i]);
/* Remove entry from hashtable */
diff --git a/src/backend/storage/freespace/fsmpage.c b/src/backend/storage/freespace/fsmpage.c
index cf7f03f..65eb422 100644
--- a/src/backend/storage/freespace/fsmpage.c
+++ b/src/backend/storage/freespace/fsmpage.c
@@ -268,13 +268,13 @@ restart:
*
* Fix the corruption and restart.
*/
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forknum;
BlockNumber blknum;
BufferGetTag(buf, &rnode, &forknum, &blknum);
elog(DEBUG1, "fixing corrupt FSM block %u, relation %u/%u/%u",
- blknum, rnode.spcNode, rnode.dbNode, rnode.relNode);
+ blknum, rnode.node.spcNode, rnode.node.dbNode, rnode.node.relNode);
/* make sure we hold an exclusive lock */
if (!exclusive_lock_held)
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 07f3c93..204c4cb 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,48 @@ 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)
+ {
+ /* Remove relation pages from shared buffers */
+ DropRelFileNodesAllBuffers(&rel->rnode, 1);
+
+ /* 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 +273,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 +522,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 +546,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 +723,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 +814,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 2488607..86e8fca 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 0cc9ede..1dff0c8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15593,8 +15593,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/buf_internals.h b/src/include/storage/buf_internals.h
index df2dda7..7adb96b 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -90,16 +90,17 @@
*/
typedef struct buftag
{
- RelFileNode rnode; /* physical relation identifier */
+ RelFileNodeBackend rnode; /* physical relation identifier */
ForkNumber forkNum;
BlockNumber blockNum; /* blknum relative to begin of reln */
} BufferTag;
#define CLEAR_BUFFERTAG(a) \
( \
- (a).rnode.spcNode = InvalidOid, \
- (a).rnode.dbNode = InvalidOid, \
- (a).rnode.relNode = InvalidOid, \
+ (a).rnode.node.spcNode = InvalidOid, \
+ (a).rnode.node.dbNode = InvalidOid, \
+ (a).rnode.node.relNode = InvalidOid, \
+ (a).rnode.backend = InvalidBackendId, \
(a).forkNum = InvalidForkNumber, \
(a).blockNum = InvalidBlockNumber \
)
@@ -113,7 +114,7 @@ typedef struct buftag
#define BUFFERTAGS_EQUAL(a,b) \
( \
- RelFileNodeEquals((a).rnode, (b).rnode) && \
+ RelFileNodeBackendEquals((a).rnode, (b).rnode) && \
(a).blockNum == (b).blockNum && \
(a).forkNum == (b).forkNum \
)
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 509f4b7..3315fa0 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -205,7 +205,7 @@ extern XLogRecPtr BufferGetLSNAtomic(Buffer buffer);
extern void PrintPinnedBufs(void);
#endif
extern Size BufferShmemSize(void);
-extern void BufferGetTag(Buffer buffer, RelFileNode *rnode,
+extern void BufferGetTag(Buffer buffer, RelFileNodeBackend *rnode,
ForkNumber *forknum, BlockNumber *blknum);
extern void MarkBufferDirtyHint(Buffer buffer, bool buffer_std);
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/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..3a6c78d 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 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..b7fcc99 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -172,6 +172,7 @@ test: limit
test: plpgsql
test: copy2
test: temp
+test: session_table
test: domain
test: rangefuncs
test: prepare
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;
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-06 08:31 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-08-06 08:31 UTC (permalink / raw)
To: Craig Ringer <craig@2ndquadrant.com>; +Cc: pgsql-hackers
New version of the patch with several fixes is attached.
Many thanks to Roman Zharkov for testing.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
[text/x-patch] session_tables-2.patch (61.3K, ../../8e0b539b-4b64-d2b2-08a1-ddaa3c7e7747@postgrespro.ru/2-session_tables-2.patch)
download | inline diff:
diff --git a/contrib/pg_buffercache/pg_buffercache_pages.c b/contrib/pg_buffercache/pg_buffercache_pages.c
index 1bd579f..2d93f6f 100644
--- a/contrib/pg_buffercache/pg_buffercache_pages.c
+++ b/contrib/pg_buffercache/pg_buffercache_pages.c
@@ -153,9 +153,9 @@ pg_buffercache_pages(PG_FUNCTION_ARGS)
buf_state = LockBufHdr(bufHdr);
fctx->record[i].bufferid = BufferDescriptorGetBuffer(bufHdr);
- fctx->record[i].relfilenode = bufHdr->tag.rnode.relNode;
- fctx->record[i].reltablespace = bufHdr->tag.rnode.spcNode;
- fctx->record[i].reldatabase = bufHdr->tag.rnode.dbNode;
+ fctx->record[i].relfilenode = bufHdr->tag.rnode.node.relNode;
+ fctx->record[i].reltablespace = bufHdr->tag.rnode.node.spcNode;
+ fctx->record[i].reldatabase = bufHdr->tag.rnode.node.dbNode;
fctx->record[i].forknum = bufHdr->tag.forkNum;
fctx->record[i].blocknum = bufHdr->tag.blockNum;
fctx->record[i].usagecount = BUF_STATE_GET_USAGECOUNT(buf_state);
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 38ae240..8a04954 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -608,9 +608,9 @@ apw_dump_now(bool is_bgworker, bool dump_unlogged)
if (buf_state & BM_TAG_VALID &&
((buf_state & BM_PERMANENT) || dump_unlogged))
{
- block_info_array[num_blocks].database = bufHdr->tag.rnode.dbNode;
- block_info_array[num_blocks].tablespace = bufHdr->tag.rnode.spcNode;
- block_info_array[num_blocks].filenode = bufHdr->tag.rnode.relNode;
+ block_info_array[num_blocks].database = bufHdr->tag.rnode.node.dbNode;
+ block_info_array[num_blocks].tablespace = bufHdr->tag.rnode.node.spcNode;
+ block_info_array[num_blocks].filenode = bufHdr->tag.rnode.node.relNode;
block_info_array[num_blocks].forknum = bufHdr->tag.forkNum;
block_info_array[num_blocks].blocknum = bufHdr->tag.blockNum;
++num_blocks;
diff --git a/src/backend/access/gin/ginxlog.c b/src/backend/access/gin/ginxlog.c
index c945b28..14d4e48 100644
--- a/src/backend/access/gin/ginxlog.c
+++ b/src/backend/access/gin/ginxlog.c
@@ -95,13 +95,13 @@ ginRedoInsertEntry(Buffer buffer, bool isLeaf, BlockNumber rightblkno, void *rda
if (PageAddItem(page, (Item) itup, IndexTupleSize(itup), offset, false, false) == InvalidOffsetNumber)
{
- RelFileNode node;
+ RelFileNodeBackend rnode;
ForkNumber forknum;
BlockNumber blknum;
- BufferGetTag(buffer, &node, &forknum, &blknum);
+ BufferGetTag(buffer, &rnode, &forknum, &blknum);
elog(ERROR, "failed to add item to index page in %u/%u/%u",
- node.spcNode, node.dbNode, node.relNode);
+ rnode.node.spcNode, rnode.node.dbNode, rnode.node.relNode);
}
}
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 9726020..389466e 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1028,7 +1028,8 @@ gistGetFakeLSN(Relation rel)
{
static XLogRecPtr counter = FirstNormalUnloggedLSN;
- if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+ rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION)
{
/*
* Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 09bc6fe..c60effd 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -671,6 +671,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 5962126..60f4696 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 && PageIsNew(BufferGetPage(buf)) && IsSessionRelationBackendId(rel->rd_backend))
+ _bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
+ else
+ _bt_checkpage(rel, buf);
}
else
{
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index 3ec67d4..edec8ca 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -213,6 +213,7 @@ void
XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
{
registered_buffer *regbuf;
+ RelFileNodeBackend rnode;
/* NO_IMAGE doesn't make sense with FORCE_IMAGE */
Assert(!((flags & REGBUF_FORCE_IMAGE) && (flags & (REGBUF_NO_IMAGE))));
@@ -227,7 +228,8 @@ XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
regbuf = ®istered_buffers[block_id];
- BufferGetTag(buffer, ®buf->rnode, ®buf->forkno, ®buf->block);
+ BufferGetTag(buffer, &rnode, ®buf->forkno, ®buf->block);
+ regbuf->rnode = rnode.node;
regbuf->page = BufferGetPage(buffer);
regbuf->flags = flags;
regbuf->rdata_tail = (XLogRecData *) ®buf->rdata_head;
@@ -919,7 +921,7 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std)
int flags;
PGAlignedBlock copied_buffer;
char *origdata = (char *) BufferGetBlock(buffer);
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forkno;
BlockNumber blkno;
@@ -948,7 +950,7 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std)
flags |= REGBUF_STANDARD;
BufferGetTag(buffer, &rnode, &forkno, &blkno);
- XLogRegisterBlock(0, &rnode, forkno, blkno, copied_buffer.data, flags);
+ XLogRegisterBlock(0, &rnode.node, forkno, blkno, copied_buffer.data, flags);
recptr = XLogInsert(RM_XLOG_ID, XLOG_FPI_FOR_HINT);
}
@@ -1009,7 +1011,7 @@ XLogRecPtr
log_newpage_buffer(Buffer buffer, bool page_std)
{
Page page = BufferGetPage(buffer);
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forkNum;
BlockNumber blkno;
@@ -1018,7 +1020,7 @@ log_newpage_buffer(Buffer buffer, bool page_std)
BufferGetTag(buffer, &rnode, &forkNum, &blkno);
- return log_newpage(&rnode, forkNum, blkno, page, page_std);
+ return log_newpage(&rnode.node, forkNum, blkno, page, page_std);
}
/*
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index a065419..8814afb 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -409,6 +409,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 99ae159..24b2438 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3612,7 +3612,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 3cc886f..a111ddc 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 cedb4ee..d11c5b3 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 0960b33..3604e4af 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,16 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
page = BufferGetPage(*buf);
+ if (rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION && PageIsNew(page))
+ {
+ /* Initialize sequence for global temporary tables */
+ Datum value[SEQ_COL_LASTCOL] = {0};
+ bool null[SEQ_COL_LASTCOL] = {false};
+ value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(1); /* start sequence with 1 */
+ HeapTuple 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 fb2be10..38a25cf 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -586,7 +586,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* Check consistency of arguments
*/
if (stmt->oncommit != ONCOMMIT_NOOP
- && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+ && stmt->relation->relpersistence != RELPERSISTENCE_TEMP
+ && stmt->relation->relpersistence != RELPERSISTENCE_SESSION)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
@@ -7678,6 +7679,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,
@@ -14082,6 +14089,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 */
@@ -14569,14 +14583,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 c97bb36..f9b2000 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3265,20 +3265,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 a9b2f8b..2f261b9 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 073f313..ae8b7fd 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2069,7 +2069,8 @@ do_autovacuum(void)
* Check if it is a temp table (presumably, of some other backend's).
* We cannot safely process other backends' temp tables.
*/
- if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP ||
+ classForm->relpersistence == RELPERSISTENCE_SESSION)
{
/*
* We just ignore it if the owning backend is still active and
@@ -2154,7 +2155,8 @@ do_autovacuum(void)
/*
* We cannot safely process other backends' temp tables, so skip 'em.
*/
- if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP ||
+ classForm->relpersistence == RELPERSISTENCE_SESSION)
continue;
relid = classForm->oid;
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index e8ffa04..2004d2f 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -3483,6 +3483,7 @@ ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
{
ReorderBufferTupleCidKey key;
ReorderBufferTupleCidEnt *ent;
+ RelFileNodeBackend rnode;
ForkNumber forkno;
BlockNumber blockno;
bool updated_mapping = false;
@@ -3496,7 +3497,8 @@ ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
* get relfilenode from the buffer, no convenient way to access it other
* than that.
*/
- BufferGetTag(buffer, &key.relnode, &forkno, &blockno);
+ BufferGetTag(buffer, &rnode, &forkno, &blockno);
+ key.relnode = rnode.node;
/* tuples can only be in the main fork */
Assert(forkno == MAIN_FORKNUM);
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 6f3a402..76ce953 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -556,7 +556,7 @@ PrefetchBuffer(Relation reln, ForkNumber forkNum, BlockNumber blockNum)
int buf_id;
/* create a tag so we can lookup the buffer */
- INIT_BUFFERTAG(newTag, reln->rd_smgr->smgr_rnode.node,
+ INIT_BUFFERTAG(newTag, reln->rd_smgr->smgr_rnode,
forkNum, blockNum);
/* determine its hash code and partition lock ID */
@@ -710,7 +710,7 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
Block bufBlock;
bool found;
bool isExtend;
- bool isLocalBuf = SmgrIsTemp(smgr);
+ bool isLocalBuf = SmgrIsTemp(smgr) && relpersistence == RELPERSISTENCE_TEMP;
*hit = false;
@@ -1010,7 +1010,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
uint32 buf_state;
/* create a tag so we can lookup the buffer */
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* determine its hash code and partition lock ID */
newHash = BufTableHashCode(&newTag);
@@ -1532,7 +1532,8 @@ ReleaseAndReadBuffer(Buffer buffer,
{
bufHdr = GetLocalBufferDescriptor(-buffer - 1);
if (bufHdr->tag.blockNum == blockNum &&
- RelFileNodeEquals(bufHdr->tag.rnode, relation->rd_node) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, relation->rd_node) &&
+ bufHdr->tag.rnode.backend == relation->rd_backend &&
bufHdr->tag.forkNum == forkNum)
return buffer;
ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
@@ -1543,7 +1544,8 @@ ReleaseAndReadBuffer(Buffer buffer,
bufHdr = GetBufferDescriptor(buffer - 1);
/* we have pin, so it's ok to examine tag without spinlock */
if (bufHdr->tag.blockNum == blockNum &&
- RelFileNodeEquals(bufHdr->tag.rnode, relation->rd_node) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, relation->rd_node) &&
+ bufHdr->tag.rnode.backend == relation->rd_backend &&
bufHdr->tag.forkNum == forkNum)
return buffer;
UnpinBuffer(bufHdr, true);
@@ -1845,8 +1847,8 @@ BufferSync(int flags)
item = &CkptBufferIds[num_to_scan++];
item->buf_id = buf_id;
- item->tsId = bufHdr->tag.rnode.spcNode;
- item->relNode = bufHdr->tag.rnode.relNode;
+ item->tsId = bufHdr->tag.rnode.node.spcNode;
+ item->relNode = bufHdr->tag.rnode.node.relNode;
item->forkNum = bufHdr->tag.forkNum;
item->blockNum = bufHdr->tag.blockNum;
}
@@ -2559,7 +2561,7 @@ PrintBufferLeakWarning(Buffer buffer)
}
/* theoretically we should lock the bufhdr here */
- path = relpathbackend(buf->tag.rnode, backend, buf->tag.forkNum);
+ path = relpathbackend(buf->tag.rnode.node, backend, buf->tag.forkNum);
buf_state = pg_atomic_read_u32(&buf->state);
elog(WARNING,
"buffer refcount leak: [%03d] "
@@ -2631,7 +2633,7 @@ BufferGetBlockNumber(Buffer buffer)
* a buffer.
*/
void
-BufferGetTag(Buffer buffer, RelFileNode *rnode, ForkNumber *forknum,
+BufferGetTag(Buffer buffer, RelFileNodeBackend *rnode, ForkNumber *forknum,
BlockNumber *blknum)
{
BufferDesc *bufHdr;
@@ -2696,7 +2698,7 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
/* Find smgr relation for buffer */
if (reln == NULL)
- reln = smgropen(buf->tag.rnode, InvalidBackendId);
+ reln = smgropen(buf->tag.rnode.node, buf->tag.rnode.backend);
TRACE_POSTGRESQL_BUFFER_FLUSH_START(buf->tag.forkNum,
buf->tag.blockNum,
@@ -2930,7 +2932,7 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber forkNum,
int i;
/* If it's a local relation, it's localbuf.c's problem. */
- if (RelFileNodeBackendIsTemp(rnode))
+ if (RelFileNodeBackendIsLocalTemp(rnode))
{
if (rnode.backend == MyBackendId)
DropRelFileNodeLocalBuffers(rnode.node, forkNum, firstDelBlock);
@@ -2958,11 +2960,11 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber forkNum,
* We could check forkNum and blockNum as well as the rnode, but the
* incremental win from doing so seems small.
*/
- if (!RelFileNodeEquals(bufHdr->tag.rnode, rnode.node))
+ if (!RelFileNodeBackendEquals(bufHdr->tag.rnode, rnode))
continue;
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rnode.node) &&
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, rnode) &&
bufHdr->tag.forkNum == forkNum &&
bufHdr->tag.blockNum >= firstDelBlock)
InvalidateBuffer(bufHdr); /* releases spinlock */
@@ -2985,24 +2987,24 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
{
int i,
n = 0;
- RelFileNode *nodes;
+ RelFileNodeBackend *nodes;
bool use_bsearch;
if (nnodes == 0)
return;
- nodes = palloc(sizeof(RelFileNode) * nnodes); /* non-local relations */
+ nodes = palloc(sizeof(RelFileNodeBackend) * nnodes); /* non-local relations */
/* If it's a local relation, it's localbuf.c's problem. */
for (i = 0; i < nnodes; i++)
{
- if (RelFileNodeBackendIsTemp(rnodes[i]))
+ if (RelFileNodeBackendIsLocalTemp(rnodes[i]))
{
if (rnodes[i].backend == MyBackendId)
DropRelFileNodeAllLocalBuffers(rnodes[i].node);
}
else
- nodes[n++] = rnodes[i].node;
+ nodes[n++] = rnodes[i];
}
/*
@@ -3025,11 +3027,11 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
/* sort the list of rnodes if necessary */
if (use_bsearch)
- pg_qsort(nodes, n, sizeof(RelFileNode), rnode_comparator);
+ pg_qsort(nodes, n, sizeof(RelFileNodeBackend), rnode_comparator);
for (i = 0; i < NBuffers; i++)
{
- RelFileNode *rnode = NULL;
+ RelFileNodeBackend *rnode = NULL;
BufferDesc *bufHdr = GetBufferDescriptor(i);
uint32 buf_state;
@@ -3044,7 +3046,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
for (j = 0; j < n; j++)
{
- if (RelFileNodeEquals(bufHdr->tag.rnode, nodes[j]))
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, nodes[j]))
{
rnode = &nodes[j];
break;
@@ -3054,7 +3056,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
else
{
rnode = bsearch((const void *) &(bufHdr->tag.rnode),
- nodes, n, sizeof(RelFileNode),
+ nodes, n, sizeof(RelFileNodeBackend),
rnode_comparator);
}
@@ -3063,7 +3065,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
continue;
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, (*rnode)))
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, (*rnode)))
InvalidateBuffer(bufHdr); /* releases spinlock */
else
UnlockBufHdr(bufHdr, buf_state);
@@ -3102,11 +3104,11 @@ DropDatabaseBuffers(Oid dbid)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (bufHdr->tag.rnode.dbNode != dbid)
+ if (bufHdr->tag.rnode.node.dbNode != dbid)
continue;
buf_state = LockBufHdr(bufHdr);
- if (bufHdr->tag.rnode.dbNode == dbid)
+ if (bufHdr->tag.rnode.node.dbNode == dbid)
InvalidateBuffer(bufHdr); /* releases spinlock */
else
UnlockBufHdr(bufHdr, buf_state);
@@ -3136,7 +3138,7 @@ PrintBufferDescs(void)
"[%02d] (freeNext=%d, rel=%s, "
"blockNum=%u, flags=0x%x, refcount=%u %d)",
i, buf->freeNext,
- relpathbackend(buf->tag.rnode, InvalidBackendId, buf->tag.forkNum),
+ relpath(buf->tag.rnode, buf->tag.forkNum),
buf->tag.blockNum, buf->flags,
buf->refcount, GetPrivateRefCount(b));
}
@@ -3204,7 +3206,8 @@ FlushRelationBuffers(Relation rel)
uint32 buf_state;
bufHdr = GetLocalBufferDescriptor(i);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+ if (RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node) &&
+ bufHdr->tag.rnode.backend == rel->rd_backend &&
((buf_state = pg_atomic_read_u32(&bufHdr->state)) &
(BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
@@ -3251,13 +3254,15 @@ FlushRelationBuffers(Relation rel)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
+ if (!RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node)
+ || bufHdr->tag.rnode.backend != rel->rd_backend)
continue;
ReservePrivateRefCountEntry();
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+ if (RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node) &&
+ bufHdr->tag.rnode.backend == rel->rd_backend &&
(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
PinBuffer_Locked(bufHdr);
@@ -3305,13 +3310,13 @@ FlushDatabaseBuffers(Oid dbid)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (bufHdr->tag.rnode.dbNode != dbid)
+ if (bufHdr->tag.rnode.node.dbNode != dbid)
continue;
ReservePrivateRefCountEntry();
buf_state = LockBufHdr(bufHdr);
- if (bufHdr->tag.rnode.dbNode == dbid &&
+ if (bufHdr->tag.rnode.node.dbNode == dbid &&
(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
PinBuffer_Locked(bufHdr);
@@ -4051,7 +4056,7 @@ AbortBufferIO(void)
/* Buffer is pinned, so we can read tag without spinlock */
char *path;
- path = relpathperm(buf->tag.rnode, buf->tag.forkNum);
+ path = relpath(buf->tag.rnode, buf->tag.forkNum);
ereport(WARNING,
(errcode(ERRCODE_IO_ERROR),
errmsg("could not write block %u of %s",
@@ -4075,7 +4080,7 @@ shared_buffer_write_error_callback(void *arg)
/* Buffer is pinned, so we can read the tag without locking the spinlock */
if (bufHdr != NULL)
{
- char *path = relpathperm(bufHdr->tag.rnode, bufHdr->tag.forkNum);
+ char *path = relpath(bufHdr->tag.rnode, bufHdr->tag.forkNum);
errcontext("writing block %u of relation %s",
bufHdr->tag.blockNum, path);
@@ -4093,7 +4098,7 @@ local_buffer_write_error_callback(void *arg)
if (bufHdr != NULL)
{
- char *path = relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ char *path = relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum);
errcontext("writing block %u of relation %s",
@@ -4108,22 +4113,27 @@ local_buffer_write_error_callback(void *arg)
static int
rnode_comparator(const void *p1, const void *p2)
{
- RelFileNode n1 = *(const RelFileNode *) p1;
- RelFileNode n2 = *(const RelFileNode *) p2;
+ RelFileNodeBackend n1 = *(const RelFileNodeBackend *) p1;
+ RelFileNodeBackend n2 = *(const RelFileNodeBackend *) p2;
- if (n1.relNode < n2.relNode)
+ if (n1.node.relNode < n2.node.relNode)
return -1;
- else if (n1.relNode > n2.relNode)
+ else if (n1.node.relNode > n2.node.relNode)
return 1;
- if (n1.dbNode < n2.dbNode)
+ if (n1.node.dbNode < n2.node.dbNode)
return -1;
- else if (n1.dbNode > n2.dbNode)
+ else if (n1.node.dbNode > n2.node.dbNode)
return 1;
- if (n1.spcNode < n2.spcNode)
+ if (n1.node.spcNode < n2.node.spcNode)
return -1;
- else if (n1.spcNode > n2.spcNode)
+ else if (n1.node.spcNode > n2.node.spcNode)
+ return 1;
+
+ if (n1.backend < n2.backend)
+ return -1;
+ else if (n1.backend > n2.backend)
return 1;
else
return 0;
@@ -4359,7 +4369,7 @@ IssuePendingWritebacks(WritebackContext *context)
next = &context->pending_writebacks[i + ahead + 1];
/* different file, stop */
- if (!RelFileNodeEquals(cur->tag.rnode, next->tag.rnode) ||
+ if (!RelFileNodeBackendEquals(cur->tag.rnode, next->tag.rnode) ||
cur->tag.forkNum != next->tag.forkNum)
break;
@@ -4378,7 +4388,7 @@ IssuePendingWritebacks(WritebackContext *context)
i += ahead;
/* and finally tell the kernel to write the data to storage */
- reln = smgropen(tag.rnode, InvalidBackendId);
+ reln = smgropen(tag.rnode.node, tag.rnode.backend);
smgrwriteback(reln, tag.forkNum, tag.blockNum, nblocks);
}
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index f5f6a29..6bd5ecb 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -68,7 +68,7 @@ LocalPrefetchBuffer(SMgrRelation smgr, ForkNumber forkNum,
BufferTag newTag; /* identity of requested block */
LocalBufferLookupEnt *hresult;
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* Initialize local buffers if first request in this session */
if (LocalBufHash == NULL)
@@ -111,7 +111,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
bool found;
uint32 buf_state;
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* Initialize local buffers if first request in this session */
if (LocalBufHash == NULL)
@@ -209,7 +209,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
Page localpage = (char *) LocalBufHdrGetBlock(bufHdr);
/* Find smgr relation for buffer */
- oreln = smgropen(bufHdr->tag.rnode, MyBackendId);
+ oreln = smgropen(bufHdr->tag.rnode.node, MyBackendId);
PageSetChecksumInplace(localpage, bufHdr->tag.blockNum);
@@ -331,14 +331,14 @@ DropRelFileNodeLocalBuffers(RelFileNode rnode, ForkNumber forkNum,
buf_state = pg_atomic_read_u32(&bufHdr->state);
if ((buf_state & BM_TAG_VALID) &&
- RelFileNodeEquals(bufHdr->tag.rnode, rnode) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, rnode) &&
bufHdr->tag.forkNum == forkNum &&
bufHdr->tag.blockNum >= firstDelBlock)
{
if (LocalRefCount[i] != 0)
elog(ERROR, "block %u of %s is still referenced (local %u)",
bufHdr->tag.blockNum,
- relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum),
LocalRefCount[i]);
/* Remove entry from hashtable */
@@ -377,12 +377,12 @@ DropRelFileNodeAllLocalBuffers(RelFileNode rnode)
buf_state = pg_atomic_read_u32(&bufHdr->state);
if ((buf_state & BM_TAG_VALID) &&
- RelFileNodeEquals(bufHdr->tag.rnode, rnode))
+ RelFileNodeEquals(bufHdr->tag.rnode.node, rnode))
{
if (LocalRefCount[i] != 0)
elog(ERROR, "block %u of %s is still referenced (local %u)",
bufHdr->tag.blockNum,
- relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum),
LocalRefCount[i]);
/* Remove entry from hashtable */
diff --git a/src/backend/storage/freespace/fsmpage.c b/src/backend/storage/freespace/fsmpage.c
index cf7f03f..65eb422 100644
--- a/src/backend/storage/freespace/fsmpage.c
+++ b/src/backend/storage/freespace/fsmpage.c
@@ -268,13 +268,13 @@ restart:
*
* Fix the corruption and restart.
*/
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forknum;
BlockNumber blknum;
BufferGetTag(buf, &rnode, &forknum, &blknum);
elog(DEBUG1, "fixing corrupt FSM block %u, relation %u/%u/%u",
- blknum, rnode.spcNode, rnode.dbNode, rnode.relNode);
+ blknum, rnode.node.spcNode, rnode.node.dbNode, rnode.node.relNode);
/* make sure we hold an exclusive lock */
if (!exclusive_lock_held)
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 07f3c93..204c4cb 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,48 @@ 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)
+ {
+ /* Remove relation pages from shared buffers */
+ DropRelFileNodesAllBuffers(&rel->rnode, 1);
+
+ /* 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 +273,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 +522,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 +546,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 +723,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 +814,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 2488607..86e8fca 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 0cc9ede..1dff0c8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15593,8 +15593,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/buf_internals.h b/src/include/storage/buf_internals.h
index df2dda7..7adb96b 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -90,16 +90,17 @@
*/
typedef struct buftag
{
- RelFileNode rnode; /* physical relation identifier */
+ RelFileNodeBackend rnode; /* physical relation identifier */
ForkNumber forkNum;
BlockNumber blockNum; /* blknum relative to begin of reln */
} BufferTag;
#define CLEAR_BUFFERTAG(a) \
( \
- (a).rnode.spcNode = InvalidOid, \
- (a).rnode.dbNode = InvalidOid, \
- (a).rnode.relNode = InvalidOid, \
+ (a).rnode.node.spcNode = InvalidOid, \
+ (a).rnode.node.dbNode = InvalidOid, \
+ (a).rnode.node.relNode = InvalidOid, \
+ (a).rnode.backend = InvalidBackendId, \
(a).forkNum = InvalidForkNumber, \
(a).blockNum = InvalidBlockNumber \
)
@@ -113,7 +114,7 @@ typedef struct buftag
#define BUFFERTAGS_EQUAL(a,b) \
( \
- RelFileNodeEquals((a).rnode, (b).rnode) && \
+ RelFileNodeBackendEquals((a).rnode, (b).rnode) && \
(a).blockNum == (b).blockNum && \
(a).forkNum == (b).forkNum \
)
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 509f4b7..3315fa0 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -205,7 +205,7 @@ extern XLogRecPtr BufferGetLSNAtomic(Buffer buffer);
extern void PrintPinnedBufs(void);
#endif
extern Size BufferShmemSize(void);
-extern void BufferGetTag(Buffer buffer, RelFileNode *rnode,
+extern void BufferGetTag(Buffer buffer, RelFileNodeBackend *rnode,
ForkNumber *forknum, BlockNumber *blknum);
extern void MarkBufferDirtyHint(Buffer buffer, bool buffer_std);
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/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;
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-08 02:40 Craig Ringer <craig@2ndquadrant.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Craig Ringer @ 2019-08-08 02:40 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: pgsql-hackers
On Tue, 6 Aug 2019 at 16:32, Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
wrote:
> New version of the patch with several fixes is attached.
> Many thanks to Roman Zharkov for testing.
>
FWIW I still don't understand your argument with regards to using
shared_buffers for temp tables having connection pooling benefits. Are you
assuming the presence of some other extension in your extended version of
PostgreSQL ? In community PostgreSQL a temp table's contents in one backend
will not be visible in another backend. So if your connection pooler in
transaction pooling mode runs txn 1 on backend 42 and it populates temp
table X, then the pooler runs the same app session's txn 2 on backend 45,
the contents of temp table X are not visible anymore.
Can you explain? Because AFAICS so long as temp table contents are
backend-private there's absolutely no point ever using shared buffers for
their contents.
Perhaps you mean that in a connection pooling case, each backend may land
up filling up temp buffers with contents from *multiple different temp
tables*? If so, sure, I get that, but the answer there seems to be to
improve eviction and memory accounting, not make backends waste precious
shared_buffers space on non-shareable data.
Anyhow, I strongly suggest you simplify the feature to add the basic global
temp table feature so the need to change pg_class, pg_attribute etc to use
temp tables is removed, but separate changes to temp table memory handling
etc into a follow-up patch. That'll make it smaller and easier to review
and merge too. The two changes are IMO logically quite separate anyway.
Come to think of it, I think connection poolers might benefit from an
extension to the DISCARD command, say "DISCARD TEMP_BUFFERS", which evicts
temp table buffers from memory *without* dropping the temp tables. If
they're currently in-memory tuplestores they'd be written out and evicted.
That way a connection pooler could "clean" the backend, at the cost of some
probably pretty cheap buffered writes to the system buffer cache. The
kernel might not even bother to write out the buffercache and it won't be
forced to do so by fsync, checkpoints, etc, nor will the writes go via WAL
so such evictions could be pretty cheap - and if not under lots of memory
pressure the backend could often read the temp table back in from system
buffer cache without disk I/O.
That's my suggestion for how to solve your pooler problem, assuming I've
understood it correctly.
Along these lines I suggest adding the following to DISCARD at some point,
obviously not as part of your patch:
* DISCARD TEMP_BUFFERS
* DISCARD SHARED_BUFFERS
* DISCARD TEMP_FILES
* DISCARD CATALOG_CACHE
* DISCARD HOLD_CURSORS
* DISCARD ADVISORY_LOCKS
where obviously DISCARD SHARED_BUFFERS would be superuser-only and evict
only clean buffers.
(Also, if we extend DISCARD lets also it to be written as DISCARD (LIST,
OF, THINGS, TO, DISCARD) so that we can make the syntax extensible for
plugins in future).
Thoughts?
Would DISCARD TEMP_BUFFERS meet your needs?
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-08 07:03 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Craig Ringer <craig@2ndquadrant.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-08-08 07:03 UTC (permalink / raw)
To: Craig Ringer <craig@2ndquadrant.com>; +Cc: pgsql-hackers
On 08.08.2019 5:40, Craig Ringer wrote:
> On Tue, 6 Aug 2019 at 16:32, Konstantin Knizhnik
> <k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> wrote:
>
> New version of the patch with several fixes is attached.
> Many thanks to Roman Zharkov for testing.
>
>
> FWIW I still don't understand your argument with regards to using
> shared_buffers for temp tables having connection pooling benefits. Are
> you assuming the presence of some other extension in your extended
> version of PostgreSQL ? In community PostgreSQL a temp table's
> contents in one backend will not be visible in another backend. So if
> your connection pooler in transaction pooling mode runs txn 1 on
> backend 42 and it populates temp table X, then the pooler runs the
> same app session's txn 2 on backend 45, the contents of temp table X
> are not visible anymore.
Certainly here I mean built-in connection pooler which is not currently
present in Postgres,
but it is part of PgPRO-EE and there is my patch for vanilla at commitfest:
https://commitfest.postgresql.org/24/2067
>
> Can you explain? Because AFAICS so long as temp table contents are
> backend-private there's absolutely no point ever using shared buffers
> for their contents.
>
Sure, there is no such problem with temporary tables now.
There is another problem: you can not use temporary table with any
existed connection poolers (pgbouncer,...) with pooling level other than
session unless temporary table is used inside one transaction.
One of the advantages of built-in connection pooler is that it can
provide session semantic (GUCs, prepared statement, temporary
tables,...) with limited number of backends (smaller than number of
sessions).
In PgPRO-EE this problem was solved by binding session to backend. I.e.
one backend can manage multiple sessions,
but session can not migrate to another backend. The drawback of such
solution is obvious: one long living transaction can block transactions
of all other sessions scheduled to this backend.
Possibility to migrate session to another backend is one of the obvious
solutions of the problem. But the main show stopper for it is temporary
tables.
This is why I consider moving temporary tables to shared buffers as
very important step.
In vanilla version of built-in connection pooler situation is slightly
different.
Right now if client is using temporary tables without "ON COMMIT DROP"
clause, backend is marked as "tainted" and is pinned for this session.
So it is actually excluded from connection pool and servers only this
session. Once again - if I will be able to access temporary table data
from other backend, there will be no need to mark backend as tainted in
this case.
Certainly it also requires shared metadata. And here we come to the
concept of global temp tables (if we forget for a moment that global
temp tables were "invented" long time ago by Oracle and many other DBMSes:)
> Perhaps you mean that in a connection pooling case, each backend may
> land up filling up temp buffers with contents from *multiple different
> temp tables*? If so, sure, I get that, but the answer there seems to
> be to improve eviction and memory accounting, not make backends waste
> precious shared_buffers space on non-shareable data.
>
> Anyhow, I strongly suggest you simplify the feature to add the basic
> global temp table feature so the need to change pg_class, pg_attribute
> etc to use temp tables is removed, but separate changes to temp table
> memory handling etc into a follow-up patch. That'll make it smaller
> and easier to review and merge too. The two changes are IMO logically
> quite separate anyway.
I agree that them are separate.
But even if we forget about built-in connection pooler, don't you think
that possibility to use parallel query plans for temporary tables is
itself strong enough motivation to access global temp table through
shared buffers
(while still supporting private page pool for local temp tables). So
both approaches (shared vs. private buffers) have their pros and
contras. This is why it seems to be reasonable to support both of them
and let user to make choice most suitable for concrete application.
Certainly it is possible to provide "global shared temp tables" and
"global private temp tables". But IMHO it is overkill.
>
> Come to think of it, I think connection poolers might benefit from an
> extension to the DISCARD command, say "DISCARD TEMP_BUFFERS", which
> evicts temp table buffers from memory *without* dropping the temp
> tables. If they're currently in-memory tuplestores they'd be written
> out and evicted. That way a connection pooler could "clean" the
> backend, at the cost of some probably pretty cheap buffered writes to
> the system buffer cache. The kernel might not even bother to write out
> the buffercache and it won't be forced to do so by fsync, checkpoints,
> etc, nor will the writes go via WAL so such evictions could be pretty
> cheap - and if not under lots of memory pressure the backend could
> often read the temp table back in from system buffer cache without
> disk I/O.
Yes, this is one of th possible solutions for session migration. But
frankly speaking flushing local buffers on each session reschedule seems
to be not so good solution. Even if OS file cache is large enough and
flushed buffers are still present in memory (but them will be written
to the disk in this case even if data of temp table is not intended to
be persisted).
>
> That's my suggestion for how to solve your pooler problem, assuming
> I've understood it correctly.
>
> Along these lines I suggest adding the following to DISCARD at some
> point, obviously not as part of your patch:
>
> * DISCARD TEMP_BUFFERS
> * DISCARD SHARED_BUFFERS
> * DISCARD TEMP_FILES
> * DISCARD CATALOG_CACHE
> * DISCARD HOLD_CURSORS
> * DISCARD ADVISORY_LOCKS
>
> where obviously DISCARD SHARED_BUFFERS would be superuser-only and
> evict only clean buffers.
>
> (Also, if we extend DISCARD lets also it to be written as DISCARD
> (LIST, OF, THINGS, TO, DISCARD) so that we can make the syntax
> extensible for plugins in future).
>
> Thoughts?
>
> Would DISCARD TEMP_BUFFERS meet your needs?
>
Actually I have already implemented DropLocalBuffers function (three
line of code:)
void
DropLocalBuffers(void)
{
RelFileNode rnode;
rnode.relNode = InvalidOid; /* drop all local buffers */
DropRelFileNodeAllLocalBuffers(rnode);
}
for yet another Postgres extension which is not yet included even in
PgPRO-EE - SnapFS: support of database snapshots.
I do not think that we need such command at user level (i.e. have
correspondent SQL command).
But, as I already wrote above, I do not consider flushing all buffers on
session reschedule as acceptable solution.
And moreover, just flushing buffers is not enough. There is still some
smgr stuff associated with this relation which is local to the backend.
We in any case has to make some changes to be able to access temporary
data from other backend even if data is flushed to the file system.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-09 05:34 Craig Ringer <craig@2ndquadrant.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Craig Ringer @ 2019-08-09 05:34 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: pgsql-hackers
On Thu, 8 Aug 2019 at 15:03, Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
wrote:
>
>
> On 08.08.2019 5:40, Craig Ringer wrote:
>
> On Tue, 6 Aug 2019 at 16:32, Konstantin Knizhnik <
> k.knizhnik@postgrespro.ru> wrote:
>
>> New version of the patch with several fixes is attached.
>> Many thanks to Roman Zharkov for testing.
>>
>
> FWIW I still don't understand your argument with regards to using
> shared_buffers for temp tables having connection pooling benefits. Are you
> assuming the presence of some other extension in your extended version of
> PostgreSQL ? In community PostgreSQL a temp table's contents in one backend
> will not be visible in another backend. So if your connection pooler in
> transaction pooling mode runs txn 1 on backend 42 and it populates temp
> table X, then the pooler runs the same app session's txn 2 on backend 45,
> the contents of temp table X are not visible anymore.
>
>
> Certainly here I mean built-in connection pooler which is not currently
> present in Postgres,
> but it is part of PgPRO-EE and there is my patch for vanilla at commitfest:
> https://commitfest.postgresql.org/24/2067
>
OK, that's what I assumed.
You're trying to treat this change as if it's a given that the other
functionality you want/propose is present in core or will be present in
core. That's far from given. My suggestion is to split it up so that the
parts can be reviewed and committed separately.
> In PgPRO-EE this problem was solved by binding session to backend. I.e.
> one backend can manage multiple sessions,
> but session can not migrate to another backend. The drawback of such
> solution is obvious: one long living transaction can block transactions of
> all other sessions scheduled to this backend.
> Possibility to migrate session to another backend is one of the obvious
> solutions of the problem. But the main show stopper for it is temporary
> tables.
> This is why I consider moving temporary tables to shared buffers as very
> important step.
>
I can see why it's important for your use case.
I am not disagreeing.
I am however strongly suggesting that your patch has two fairly distinct
functional changes in it, and you should separate them out.
* Introduce global temp tables, a new relkind that works like a temp table
but doesn't require catalog changes. Uses per-backend relfilenode and
cleanup like existing temp tables. You could extend the relmapper to handle
the mapping of relation oid to per-backend relfilenode.
* Associate global temp tables with session state and manage them in
shared_buffers so they can work with the in-core connection pooler (if
committed)
Historically we've had a few efforts to get in-core connection pooling that
haven't gone anywhere. Without your pooler patch the changes you make to
use shared_buffers etc are going to be unhelpful at best, if not actively
harmful to performance, and will add unnecessary complexity. So I think
there's a logical series of patches here:
* global temp table relkind and support for it
* session state separation
* connection pooling
* pooler-friendly temp tables in shared_buffers
Make sense?
> But even if we forget about built-in connection pooler, don't you think
> that possibility to use parallel query plans for temporary tables is itself
> strong enough motivation to access global temp table through shared buffers?
>
I can see a way to share temp tables across parallel query backends being
very useful for DW/OLAP workloads, yes. But I don't know if putting them in
shared_buffers is the right answer for that. We have DSM/DSA, we have
shm_mq, various options for making temp buffers share-able with parallel
worker backends.
I'm suggesting that you not tie the whole (very useful) global temp tables
feature to this, but instead split it up into logical units that can be
understood, reviewed and committed separately.
I would gladly participate in review.
Would DISCARD TEMP_BUFFERS meet your needs?
>
>
> Actually I have already implemented DropLocalBuffers function (three line
> of code:)
>
> [...]
>
I do not think that we need such command at user level (i.e. have
> correspondent SQL command).
>
I'd be very happy to have it personally, but don't think it needs to be
tied in with your patch set here. Maybe I can cook up a patch soon.
--
Craig Ringer http://www.2ndQuadrant.com/
2ndQuadrant - PostgreSQL Solutions for the Enterprise
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-09 14:07 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Craig Ringer <craig@2ndquadrant.com>
0 siblings, 2 replies; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-08-09 14:07 UTC (permalink / raw)
To: Craig Ringer <craig@2ndquadrant.com>; +Cc: pgsql-hackers
On 09.08.2019 8:34, Craig Ringer wrote:
> On Thu, 8 Aug 2019 at 15:03, Konstantin Knizhnik
> <k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> wrote:
>
>
>
> On 08.08.2019 5:40, Craig Ringer wrote:
>> On Tue, 6 Aug 2019 at 16:32, Konstantin Knizhnik
>> <k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> wrote:
>>
>> New version of the patch with several fixes is attached.
>> Many thanks to Roman Zharkov for testing.
>>
>>
>> FWIW I still don't understand your argument with regards to using
>> shared_buffers for temp tables having connection pooling
>> benefits. Are you assuming the presence of some other extension
>> in your extended version of PostgreSQL ? In community PostgreSQL
>> a temp table's contents in one backend will not be visible in
>> another backend. So if your connection pooler in transaction
>> pooling mode runs txn 1 on backend 42 and it populates temp table
>> X, then the pooler runs the same app session's txn 2 on backend
>> 45, the contents of temp table X are not visible anymore.
>
> Certainly here I mean built-in connection pooler which is not
> currently present in Postgres,
> but it is part of PgPRO-EE and there is my patch for vanilla at
> commitfest:
> https://commitfest.postgresql.org/24/2067
>
>
> OK, that's what I assumed.
>
> You're trying to treat this change as if it's a given that the other
> functionality you want/propose is present in core or will be present
> in core. That's far from given. My suggestion is to split it up so
> that the parts can be reviewed and committed separately.
>
> In PgPRO-EE this problem was solved by binding session to backend.
> I.e. one backend can manage multiple sessions,
> but session can not migrate to another backend. The drawback of
> such solution is obvious: one long living transaction can block
> transactions of all other sessions scheduled to this backend.
> Possibility to migrate session to another backend is one of the
> obvious solutions of the problem. But the main show stopper for it
> is temporary tables.
> This is why I consider moving temporary tables to shared buffers
> as very important step.
>
>
> I can see why it's important for your use case.
>
> I am not disagreeing.
>
> I am however strongly suggesting that your patch has two fairly
> distinct functional changes in it, and you should separate them out.
>
> * Introduce global temp tables, a new relkind that works like a temp
> table but doesn't require catalog changes. Uses per-backend
> relfilenode and cleanup like existing temp tables. You could extend
> the relmapper to handle the mapping of relation oid to per-backend
> relfilenode.
>
> * Associate global temp tables with session state and manage them in
> shared_buffers so they can work with the in-core connection pooler (if
> committed)
>
> Historically we've had a few efforts to get in-core connection pooling
> that haven't gone anywhere. Without your pooler patch the changes you
> make to use shared_buffers etc are going to be unhelpful at best, if
> not actively harmful to performance, and will add unnecessary
> complexity. So I think there's a logical series of patches here:
>
> * global temp table relkind and support for it
> * session state separation
> * connection pooling
> * pooler-friendly temp tables in shared_buffers
>
> Make sense?
>
> But even if we forget about built-in connection pooler, don't you
> think that possibility to use parallel query plans for temporary
> tables is itself strong enough motivation to access global temp
> table through shared buffers?
>
>
> I can see a way to share temp tables across parallel query backends
> being very useful for DW/OLAP workloads, yes. But I don't know if
> putting them in shared_buffers is the right answer for that. We have
> DSM/DSA, we have shm_mq, various options for making temp buffers
> share-able with parallel worker backends.
>
> I'm suggesting that you not tie the whole (very useful) global temp
> tables feature to this, but instead split it up into logical units
> that can be understood, reviewed and committed separately.
>
> I would gladly participate in review.
Ok, here it is: global_private_temp-1.patch
Also I have attached updated version of the global temp tables with
shared buffers - global_shared_temp-1.patch
It is certainly larger (~2k lines vs. 1.5k lines) because it is changing
BufferTag and related functions.
But I do not think that this different is so critical.
Still have a wish to kill two birds with one stone:)
> --
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
[text/x-patch] global_shared_temp-1.patch (72.8K, ../../94a19e7b-d99a-110c-8e5b-f5068682b474@postgrespro.ru/3-global_shared_temp-1.patch)
download | inline diff:
diff --git a/contrib/pg_buffercache/pg_buffercache_pages.c b/contrib/pg_buffercache/pg_buffercache_pages.c
index 1bd579f..2d93f6f 100644
--- a/contrib/pg_buffercache/pg_buffercache_pages.c
+++ b/contrib/pg_buffercache/pg_buffercache_pages.c
@@ -153,9 +153,9 @@ pg_buffercache_pages(PG_FUNCTION_ARGS)
buf_state = LockBufHdr(bufHdr);
fctx->record[i].bufferid = BufferDescriptorGetBuffer(bufHdr);
- fctx->record[i].relfilenode = bufHdr->tag.rnode.relNode;
- fctx->record[i].reltablespace = bufHdr->tag.rnode.spcNode;
- fctx->record[i].reldatabase = bufHdr->tag.rnode.dbNode;
+ fctx->record[i].relfilenode = bufHdr->tag.rnode.node.relNode;
+ fctx->record[i].reltablespace = bufHdr->tag.rnode.node.spcNode;
+ fctx->record[i].reldatabase = bufHdr->tag.rnode.node.dbNode;
fctx->record[i].forknum = bufHdr->tag.forkNum;
fctx->record[i].blocknum = bufHdr->tag.blockNum;
fctx->record[i].usagecount = BUF_STATE_GET_USAGECOUNT(buf_state);
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 38ae240..8a04954 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -608,9 +608,9 @@ apw_dump_now(bool is_bgworker, bool dump_unlogged)
if (buf_state & BM_TAG_VALID &&
((buf_state & BM_PERMANENT) || dump_unlogged))
{
- block_info_array[num_blocks].database = bufHdr->tag.rnode.dbNode;
- block_info_array[num_blocks].tablespace = bufHdr->tag.rnode.spcNode;
- block_info_array[num_blocks].filenode = bufHdr->tag.rnode.relNode;
+ block_info_array[num_blocks].database = bufHdr->tag.rnode.node.dbNode;
+ block_info_array[num_blocks].tablespace = bufHdr->tag.rnode.node.spcNode;
+ block_info_array[num_blocks].filenode = bufHdr->tag.rnode.node.relNode;
block_info_array[num_blocks].forknum = bufHdr->tag.forkNum;
block_info_array[num_blocks].blocknum = bufHdr->tag.blockNum;
++num_blocks;
diff --git a/src/backend/access/gin/ginxlog.c b/src/backend/access/gin/ginxlog.c
index c945b28..14d4e48 100644
--- a/src/backend/access/gin/ginxlog.c
+++ b/src/backend/access/gin/ginxlog.c
@@ -95,13 +95,13 @@ ginRedoInsertEntry(Buffer buffer, bool isLeaf, BlockNumber rightblkno, void *rda
if (PageAddItem(page, (Item) itup, IndexTupleSize(itup), offset, false, false) == InvalidOffsetNumber)
{
- RelFileNode node;
+ RelFileNodeBackend rnode;
ForkNumber forknum;
BlockNumber blknum;
- BufferGetTag(buffer, &node, &forknum, &blknum);
+ BufferGetTag(buffer, &rnode, &forknum, &blknum);
elog(ERROR, "failed to add item to index page in %u/%u/%u",
- node.spcNode, node.dbNode, node.relNode);
+ rnode.node.spcNode, rnode.node.dbNode, rnode.node.relNode);
}
}
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 9726020..389466e 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1028,7 +1028,8 @@ gistGetFakeLSN(Relation rel)
{
static XLogRecPtr counter = FirstNormalUnloggedLSN;
- if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+ rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION)
{
/*
* Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 09bc6fe..c60effd 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -671,6 +671,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 5962126..60f4696 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 && PageIsNew(BufferGetPage(buf)) && IsSessionRelationBackendId(rel->rd_backend))
+ _bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
+ else
+ _bt_checkpage(rel, buf);
}
else
{
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index 3ec67d4..edec8ca 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -213,6 +213,7 @@ void
XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
{
registered_buffer *regbuf;
+ RelFileNodeBackend rnode;
/* NO_IMAGE doesn't make sense with FORCE_IMAGE */
Assert(!((flags & REGBUF_FORCE_IMAGE) && (flags & (REGBUF_NO_IMAGE))));
@@ -227,7 +228,8 @@ XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
regbuf = ®istered_buffers[block_id];
- BufferGetTag(buffer, ®buf->rnode, ®buf->forkno, ®buf->block);
+ BufferGetTag(buffer, &rnode, ®buf->forkno, ®buf->block);
+ regbuf->rnode = rnode.node;
regbuf->page = BufferGetPage(buffer);
regbuf->flags = flags;
regbuf->rdata_tail = (XLogRecData *) ®buf->rdata_head;
@@ -919,7 +921,7 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std)
int flags;
PGAlignedBlock copied_buffer;
char *origdata = (char *) BufferGetBlock(buffer);
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forkno;
BlockNumber blkno;
@@ -948,7 +950,7 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std)
flags |= REGBUF_STANDARD;
BufferGetTag(buffer, &rnode, &forkno, &blkno);
- XLogRegisterBlock(0, &rnode, forkno, blkno, copied_buffer.data, flags);
+ XLogRegisterBlock(0, &rnode.node, forkno, blkno, copied_buffer.data, flags);
recptr = XLogInsert(RM_XLOG_ID, XLOG_FPI_FOR_HINT);
}
@@ -1009,7 +1011,7 @@ XLogRecPtr
log_newpage_buffer(Buffer buffer, bool page_std)
{
Page page = BufferGetPage(buffer);
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forkNum;
BlockNumber blkno;
@@ -1018,7 +1020,7 @@ log_newpage_buffer(Buffer buffer, bool page_std)
BufferGetTag(buffer, &rnode, &forkNum, &blkno);
- return log_newpage(&rnode, forkNum, blkno, page, page_std);
+ return log_newpage(&rnode.node, forkNum, blkno, page, page_std);
}
/*
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index a065419..8814afb 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -409,6 +409,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 99ae159..24b2438 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3612,7 +3612,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 3cc886f..a111ddc 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 cedb4ee..d11c5b3 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 0960b33..d700650 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 (rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION && PageIsNew(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 fb2be10..a7d0e99 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -586,7 +586,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* Check consistency of arguments
*/
if (stmt->oncommit != ONCOMMIT_NOOP
- && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+ && stmt->relation->relpersistence != RELPERSISTENCE_TEMP
+ && stmt->relation->relpersistence != RELPERSISTENCE_SESSION)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
@@ -1772,7 +1773,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);
@@ -7678,6 +7680,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,
@@ -14082,6 +14090,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 */
@@ -14569,14 +14584,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 c97bb36..f9b2000 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3265,20 +3265,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 a9b2f8b..2f261b9 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 073f313..ae8b7fd 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2069,7 +2069,8 @@ do_autovacuum(void)
* Check if it is a temp table (presumably, of some other backend's).
* We cannot safely process other backends' temp tables.
*/
- if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP ||
+ classForm->relpersistence == RELPERSISTENCE_SESSION)
{
/*
* We just ignore it if the owning backend is still active and
@@ -2154,7 +2155,8 @@ do_autovacuum(void)
/*
* We cannot safely process other backends' temp tables, so skip 'em.
*/
- if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP ||
+ classForm->relpersistence == RELPERSISTENCE_SESSION)
continue;
relid = classForm->oid;
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index e8ffa04..2004d2f 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -3483,6 +3483,7 @@ ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
{
ReorderBufferTupleCidKey key;
ReorderBufferTupleCidEnt *ent;
+ RelFileNodeBackend rnode;
ForkNumber forkno;
BlockNumber blockno;
bool updated_mapping = false;
@@ -3496,7 +3497,8 @@ ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
* get relfilenode from the buffer, no convenient way to access it other
* than that.
*/
- BufferGetTag(buffer, &key.relnode, &forkno, &blockno);
+ BufferGetTag(buffer, &rnode, &forkno, &blockno);
+ key.relnode = rnode.node;
/* tuples can only be in the main fork */
Assert(forkno == MAIN_FORKNUM);
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 6f3a402..76ce953 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -556,7 +556,7 @@ PrefetchBuffer(Relation reln, ForkNumber forkNum, BlockNumber blockNum)
int buf_id;
/* create a tag so we can lookup the buffer */
- INIT_BUFFERTAG(newTag, reln->rd_smgr->smgr_rnode.node,
+ INIT_BUFFERTAG(newTag, reln->rd_smgr->smgr_rnode,
forkNum, blockNum);
/* determine its hash code and partition lock ID */
@@ -710,7 +710,7 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
Block bufBlock;
bool found;
bool isExtend;
- bool isLocalBuf = SmgrIsTemp(smgr);
+ bool isLocalBuf = SmgrIsTemp(smgr) && relpersistence == RELPERSISTENCE_TEMP;
*hit = false;
@@ -1010,7 +1010,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
uint32 buf_state;
/* create a tag so we can lookup the buffer */
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* determine its hash code and partition lock ID */
newHash = BufTableHashCode(&newTag);
@@ -1532,7 +1532,8 @@ ReleaseAndReadBuffer(Buffer buffer,
{
bufHdr = GetLocalBufferDescriptor(-buffer - 1);
if (bufHdr->tag.blockNum == blockNum &&
- RelFileNodeEquals(bufHdr->tag.rnode, relation->rd_node) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, relation->rd_node) &&
+ bufHdr->tag.rnode.backend == relation->rd_backend &&
bufHdr->tag.forkNum == forkNum)
return buffer;
ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
@@ -1543,7 +1544,8 @@ ReleaseAndReadBuffer(Buffer buffer,
bufHdr = GetBufferDescriptor(buffer - 1);
/* we have pin, so it's ok to examine tag without spinlock */
if (bufHdr->tag.blockNum == blockNum &&
- RelFileNodeEquals(bufHdr->tag.rnode, relation->rd_node) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, relation->rd_node) &&
+ bufHdr->tag.rnode.backend == relation->rd_backend &&
bufHdr->tag.forkNum == forkNum)
return buffer;
UnpinBuffer(bufHdr, true);
@@ -1845,8 +1847,8 @@ BufferSync(int flags)
item = &CkptBufferIds[num_to_scan++];
item->buf_id = buf_id;
- item->tsId = bufHdr->tag.rnode.spcNode;
- item->relNode = bufHdr->tag.rnode.relNode;
+ item->tsId = bufHdr->tag.rnode.node.spcNode;
+ item->relNode = bufHdr->tag.rnode.node.relNode;
item->forkNum = bufHdr->tag.forkNum;
item->blockNum = bufHdr->tag.blockNum;
}
@@ -2559,7 +2561,7 @@ PrintBufferLeakWarning(Buffer buffer)
}
/* theoretically we should lock the bufhdr here */
- path = relpathbackend(buf->tag.rnode, backend, buf->tag.forkNum);
+ path = relpathbackend(buf->tag.rnode.node, backend, buf->tag.forkNum);
buf_state = pg_atomic_read_u32(&buf->state);
elog(WARNING,
"buffer refcount leak: [%03d] "
@@ -2631,7 +2633,7 @@ BufferGetBlockNumber(Buffer buffer)
* a buffer.
*/
void
-BufferGetTag(Buffer buffer, RelFileNode *rnode, ForkNumber *forknum,
+BufferGetTag(Buffer buffer, RelFileNodeBackend *rnode, ForkNumber *forknum,
BlockNumber *blknum)
{
BufferDesc *bufHdr;
@@ -2696,7 +2698,7 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
/* Find smgr relation for buffer */
if (reln == NULL)
- reln = smgropen(buf->tag.rnode, InvalidBackendId);
+ reln = smgropen(buf->tag.rnode.node, buf->tag.rnode.backend);
TRACE_POSTGRESQL_BUFFER_FLUSH_START(buf->tag.forkNum,
buf->tag.blockNum,
@@ -2930,7 +2932,7 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber forkNum,
int i;
/* If it's a local relation, it's localbuf.c's problem. */
- if (RelFileNodeBackendIsTemp(rnode))
+ if (RelFileNodeBackendIsLocalTemp(rnode))
{
if (rnode.backend == MyBackendId)
DropRelFileNodeLocalBuffers(rnode.node, forkNum, firstDelBlock);
@@ -2958,11 +2960,11 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber forkNum,
* We could check forkNum and blockNum as well as the rnode, but the
* incremental win from doing so seems small.
*/
- if (!RelFileNodeEquals(bufHdr->tag.rnode, rnode.node))
+ if (!RelFileNodeBackendEquals(bufHdr->tag.rnode, rnode))
continue;
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rnode.node) &&
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, rnode) &&
bufHdr->tag.forkNum == forkNum &&
bufHdr->tag.blockNum >= firstDelBlock)
InvalidateBuffer(bufHdr); /* releases spinlock */
@@ -2985,24 +2987,24 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
{
int i,
n = 0;
- RelFileNode *nodes;
+ RelFileNodeBackend *nodes;
bool use_bsearch;
if (nnodes == 0)
return;
- nodes = palloc(sizeof(RelFileNode) * nnodes); /* non-local relations */
+ nodes = palloc(sizeof(RelFileNodeBackend) * nnodes); /* non-local relations */
/* If it's a local relation, it's localbuf.c's problem. */
for (i = 0; i < nnodes; i++)
{
- if (RelFileNodeBackendIsTemp(rnodes[i]))
+ if (RelFileNodeBackendIsLocalTemp(rnodes[i]))
{
if (rnodes[i].backend == MyBackendId)
DropRelFileNodeAllLocalBuffers(rnodes[i].node);
}
else
- nodes[n++] = rnodes[i].node;
+ nodes[n++] = rnodes[i];
}
/*
@@ -3025,11 +3027,11 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
/* sort the list of rnodes if necessary */
if (use_bsearch)
- pg_qsort(nodes, n, sizeof(RelFileNode), rnode_comparator);
+ pg_qsort(nodes, n, sizeof(RelFileNodeBackend), rnode_comparator);
for (i = 0; i < NBuffers; i++)
{
- RelFileNode *rnode = NULL;
+ RelFileNodeBackend *rnode = NULL;
BufferDesc *bufHdr = GetBufferDescriptor(i);
uint32 buf_state;
@@ -3044,7 +3046,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
for (j = 0; j < n; j++)
{
- if (RelFileNodeEquals(bufHdr->tag.rnode, nodes[j]))
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, nodes[j]))
{
rnode = &nodes[j];
break;
@@ -3054,7 +3056,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
else
{
rnode = bsearch((const void *) &(bufHdr->tag.rnode),
- nodes, n, sizeof(RelFileNode),
+ nodes, n, sizeof(RelFileNodeBackend),
rnode_comparator);
}
@@ -3063,7 +3065,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
continue;
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, (*rnode)))
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, (*rnode)))
InvalidateBuffer(bufHdr); /* releases spinlock */
else
UnlockBufHdr(bufHdr, buf_state);
@@ -3102,11 +3104,11 @@ DropDatabaseBuffers(Oid dbid)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (bufHdr->tag.rnode.dbNode != dbid)
+ if (bufHdr->tag.rnode.node.dbNode != dbid)
continue;
buf_state = LockBufHdr(bufHdr);
- if (bufHdr->tag.rnode.dbNode == dbid)
+ if (bufHdr->tag.rnode.node.dbNode == dbid)
InvalidateBuffer(bufHdr); /* releases spinlock */
else
UnlockBufHdr(bufHdr, buf_state);
@@ -3136,7 +3138,7 @@ PrintBufferDescs(void)
"[%02d] (freeNext=%d, rel=%s, "
"blockNum=%u, flags=0x%x, refcount=%u %d)",
i, buf->freeNext,
- relpathbackend(buf->tag.rnode, InvalidBackendId, buf->tag.forkNum),
+ relpath(buf->tag.rnode, buf->tag.forkNum),
buf->tag.blockNum, buf->flags,
buf->refcount, GetPrivateRefCount(b));
}
@@ -3204,7 +3206,8 @@ FlushRelationBuffers(Relation rel)
uint32 buf_state;
bufHdr = GetLocalBufferDescriptor(i);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+ if (RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node) &&
+ bufHdr->tag.rnode.backend == rel->rd_backend &&
((buf_state = pg_atomic_read_u32(&bufHdr->state)) &
(BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
@@ -3251,13 +3254,15 @@ FlushRelationBuffers(Relation rel)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
+ if (!RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node)
+ || bufHdr->tag.rnode.backend != rel->rd_backend)
continue;
ReservePrivateRefCountEntry();
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+ if (RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node) &&
+ bufHdr->tag.rnode.backend == rel->rd_backend &&
(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
PinBuffer_Locked(bufHdr);
@@ -3305,13 +3310,13 @@ FlushDatabaseBuffers(Oid dbid)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (bufHdr->tag.rnode.dbNode != dbid)
+ if (bufHdr->tag.rnode.node.dbNode != dbid)
continue;
ReservePrivateRefCountEntry();
buf_state = LockBufHdr(bufHdr);
- if (bufHdr->tag.rnode.dbNode == dbid &&
+ if (bufHdr->tag.rnode.node.dbNode == dbid &&
(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
PinBuffer_Locked(bufHdr);
@@ -4051,7 +4056,7 @@ AbortBufferIO(void)
/* Buffer is pinned, so we can read tag without spinlock */
char *path;
- path = relpathperm(buf->tag.rnode, buf->tag.forkNum);
+ path = relpath(buf->tag.rnode, buf->tag.forkNum);
ereport(WARNING,
(errcode(ERRCODE_IO_ERROR),
errmsg("could not write block %u of %s",
@@ -4075,7 +4080,7 @@ shared_buffer_write_error_callback(void *arg)
/* Buffer is pinned, so we can read the tag without locking the spinlock */
if (bufHdr != NULL)
{
- char *path = relpathperm(bufHdr->tag.rnode, bufHdr->tag.forkNum);
+ char *path = relpath(bufHdr->tag.rnode, bufHdr->tag.forkNum);
errcontext("writing block %u of relation %s",
bufHdr->tag.blockNum, path);
@@ -4093,7 +4098,7 @@ local_buffer_write_error_callback(void *arg)
if (bufHdr != NULL)
{
- char *path = relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ char *path = relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum);
errcontext("writing block %u of relation %s",
@@ -4108,22 +4113,27 @@ local_buffer_write_error_callback(void *arg)
static int
rnode_comparator(const void *p1, const void *p2)
{
- RelFileNode n1 = *(const RelFileNode *) p1;
- RelFileNode n2 = *(const RelFileNode *) p2;
+ RelFileNodeBackend n1 = *(const RelFileNodeBackend *) p1;
+ RelFileNodeBackend n2 = *(const RelFileNodeBackend *) p2;
- if (n1.relNode < n2.relNode)
+ if (n1.node.relNode < n2.node.relNode)
return -1;
- else if (n1.relNode > n2.relNode)
+ else if (n1.node.relNode > n2.node.relNode)
return 1;
- if (n1.dbNode < n2.dbNode)
+ if (n1.node.dbNode < n2.node.dbNode)
return -1;
- else if (n1.dbNode > n2.dbNode)
+ else if (n1.node.dbNode > n2.node.dbNode)
return 1;
- if (n1.spcNode < n2.spcNode)
+ if (n1.node.spcNode < n2.node.spcNode)
return -1;
- else if (n1.spcNode > n2.spcNode)
+ else if (n1.node.spcNode > n2.node.spcNode)
+ return 1;
+
+ if (n1.backend < n2.backend)
+ return -1;
+ else if (n1.backend > n2.backend)
return 1;
else
return 0;
@@ -4359,7 +4369,7 @@ IssuePendingWritebacks(WritebackContext *context)
next = &context->pending_writebacks[i + ahead + 1];
/* different file, stop */
- if (!RelFileNodeEquals(cur->tag.rnode, next->tag.rnode) ||
+ if (!RelFileNodeBackendEquals(cur->tag.rnode, next->tag.rnode) ||
cur->tag.forkNum != next->tag.forkNum)
break;
@@ -4378,7 +4388,7 @@ IssuePendingWritebacks(WritebackContext *context)
i += ahead;
/* and finally tell the kernel to write the data to storage */
- reln = smgropen(tag.rnode, InvalidBackendId);
+ reln = smgropen(tag.rnode.node, tag.rnode.backend);
smgrwriteback(reln, tag.forkNum, tag.blockNum, nblocks);
}
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index f5f6a29..6bd5ecb 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -68,7 +68,7 @@ LocalPrefetchBuffer(SMgrRelation smgr, ForkNumber forkNum,
BufferTag newTag; /* identity of requested block */
LocalBufferLookupEnt *hresult;
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* Initialize local buffers if first request in this session */
if (LocalBufHash == NULL)
@@ -111,7 +111,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
bool found;
uint32 buf_state;
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* Initialize local buffers if first request in this session */
if (LocalBufHash == NULL)
@@ -209,7 +209,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
Page localpage = (char *) LocalBufHdrGetBlock(bufHdr);
/* Find smgr relation for buffer */
- oreln = smgropen(bufHdr->tag.rnode, MyBackendId);
+ oreln = smgropen(bufHdr->tag.rnode.node, MyBackendId);
PageSetChecksumInplace(localpage, bufHdr->tag.blockNum);
@@ -331,14 +331,14 @@ DropRelFileNodeLocalBuffers(RelFileNode rnode, ForkNumber forkNum,
buf_state = pg_atomic_read_u32(&bufHdr->state);
if ((buf_state & BM_TAG_VALID) &&
- RelFileNodeEquals(bufHdr->tag.rnode, rnode) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, rnode) &&
bufHdr->tag.forkNum == forkNum &&
bufHdr->tag.blockNum >= firstDelBlock)
{
if (LocalRefCount[i] != 0)
elog(ERROR, "block %u of %s is still referenced (local %u)",
bufHdr->tag.blockNum,
- relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum),
LocalRefCount[i]);
/* Remove entry from hashtable */
@@ -377,12 +377,12 @@ DropRelFileNodeAllLocalBuffers(RelFileNode rnode)
buf_state = pg_atomic_read_u32(&bufHdr->state);
if ((buf_state & BM_TAG_VALID) &&
- RelFileNodeEquals(bufHdr->tag.rnode, rnode))
+ RelFileNodeEquals(bufHdr->tag.rnode.node, rnode))
{
if (LocalRefCount[i] != 0)
elog(ERROR, "block %u of %s is still referenced (local %u)",
bufHdr->tag.blockNum,
- relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum),
LocalRefCount[i]);
/* Remove entry from hashtable */
diff --git a/src/backend/storage/freespace/fsmpage.c b/src/backend/storage/freespace/fsmpage.c
index cf7f03f..65eb422 100644
--- a/src/backend/storage/freespace/fsmpage.c
+++ b/src/backend/storage/freespace/fsmpage.c
@@ -268,13 +268,13 @@ restart:
*
* Fix the corruption and restart.
*/
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forknum;
BlockNumber blknum;
BufferGetTag(buf, &rnode, &forknum, &blknum);
elog(DEBUG1, "fixing corrupt FSM block %u, relation %u/%u/%u",
- blknum, rnode.spcNode, rnode.dbNode, rnode.relNode);
+ blknum, rnode.node.spcNode, rnode.node.dbNode, rnode.node.relNode);
/* make sure we hold an exclusive lock */
if (!exclusive_lock_held)
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 07f3c93..204c4cb 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,48 @@ 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)
+ {
+ /* Remove relation pages from shared buffers */
+ DropRelFileNodesAllBuffers(&rel->rnode, 1);
+
+ /* 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 +273,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 +522,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 +546,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 +723,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 +814,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 2488607..86e8fca 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 0cc9ede..1dff0c8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15593,8 +15593,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/buf_internals.h b/src/include/storage/buf_internals.h
index df2dda7..7adb96b 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -90,16 +90,17 @@
*/
typedef struct buftag
{
- RelFileNode rnode; /* physical relation identifier */
+ RelFileNodeBackend rnode; /* physical relation identifier */
ForkNumber forkNum;
BlockNumber blockNum; /* blknum relative to begin of reln */
} BufferTag;
#define CLEAR_BUFFERTAG(a) \
( \
- (a).rnode.spcNode = InvalidOid, \
- (a).rnode.dbNode = InvalidOid, \
- (a).rnode.relNode = InvalidOid, \
+ (a).rnode.node.spcNode = InvalidOid, \
+ (a).rnode.node.dbNode = InvalidOid, \
+ (a).rnode.node.relNode = InvalidOid, \
+ (a).rnode.backend = InvalidBackendId, \
(a).forkNum = InvalidForkNumber, \
(a).blockNum = InvalidBlockNumber \
)
@@ -113,7 +114,7 @@ typedef struct buftag
#define BUFFERTAGS_EQUAL(a,b) \
( \
- RelFileNodeEquals((a).rnode, (b).rnode) && \
+ RelFileNodeBackendEquals((a).rnode, (b).rnode) && \
(a).blockNum == (b).blockNum && \
(a).forkNum == (b).forkNum \
)
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 509f4b7..3315fa0 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -205,7 +205,7 @@ extern XLogRecPtr BufferGetLSNAtomic(Buffer buffer);
extern void PrintPinnedBufs(void);
#endif
extern Size BufferShmemSize(void);
-extern void BufferGetTag(Buffer buffer, RelFileNode *rnode,
+extern void BufferGetTag(Buffer buffer, RelFileNodeBackend *rnode,
ForkNumber *forknum, BlockNumber *blknum);
extern void MarkBufferDirtyHint(Buffer buffer, bool buffer_std);
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/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 74b5077..44df4e0 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -85,3 +85,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;
[text/x-patch] global_private_temp-1.patch (50.6K, ../../94a19e7b-d99a-110c-8e5b-f5068682b474@postgrespro.ru/4-global_private_temp-1.patch)
download | inline diff:
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 9726020..389466e 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1028,7 +1028,8 @@ gistGetFakeLSN(Relation rel)
{
static XLogRecPtr counter = FirstNormalUnloggedLSN;
- if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+ rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION)
{
/*
* Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f1ff01e..e92d324 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 9c1f7de..e4a56f6 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 && PageIsNew(BufferGetPage(buf)) && IsSessionRelationBackendId(rel->rd_backend))
+ _bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
+ else
+ _bt_checkpage(rel, buf);
}
else
{
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index a065419..8814afb 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -409,6 +409,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 3e1d406..aaa2c49 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3590,7 +3590,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 3cc886f..a111ddc 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 cedb4ee..d11c5b3 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 0960b33..d700650 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 (rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION && PageIsNew(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 fb2be10..a7d0e99 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -586,7 +586,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* Check consistency of arguments
*/
if (stmt->oncommit != ONCOMMIT_NOOP
- && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+ && stmt->relation->relpersistence != RELPERSISTENCE_TEMP
+ && stmt->relation->relpersistence != RELPERSISTENCE_SESSION)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
@@ -1772,7 +1773,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);
@@ -7678,6 +7680,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,
@@ -14082,6 +14090,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 */
@@ -14569,14 +14584,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 c97bb36..f9b2000 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3265,20 +3265,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 6e5768c..ea6989b 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 073f313..ae8b7fd 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2069,7 +2069,8 @@ do_autovacuum(void)
* Check if it is a temp table (presumably, of some other backend's).
* We cannot safely process other backends' temp tables.
*/
- if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP ||
+ classForm->relpersistence == RELPERSISTENCE_SESSION)
{
/*
* We just ignore it if the owning backend is still active and
@@ -2154,7 +2155,8 @@ do_autovacuum(void)
/*
* We cannot safely process other backends' temp tables, so skip 'em.
*/
- if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP ||
+ classForm->relpersistence == RELPERSISTENCE_SESSION)
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 2488607..86e8fca 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 0cc9ede..1dff0c8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15593,8 +15593,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/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/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 69ae227..95919f8 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -87,3 +87,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;
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-10 02:12 Craig Ringer <craig@2ndquadrant.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
1 sibling, 1 reply; 525+ messages in thread
From: Craig Ringer @ 2019-08-10 02:12 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: pgsql-hackers
On Fri, 9 Aug 2019 at 22:07, Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
wrote:
>
>
> Ok, here it is: global_private_temp-1.patch
>
Fantastic.
I'll put that high on my queue.
I'd love to see something like this get in.
Doubly so if it brings us closer to being able to use temp tables on
physical read replicas, though I know there are plenty of other barriers
there (not least of which being temp tables using persistent txns not
vtxids)
Does it have a CF entry?
> Also I have attached updated version of the global temp tables with shared
> buffers - global_shared_temp-1.patch
>
Nice to see that split out. In addition to giving the first patch more hope
of being committed this time around, it'll help with readability and
testability too.
To be clear, I have long wanted to see PostgreSQL have the "session" state
abstraction you have implemented. I think it's really important for high
client count OLTP workloads, working with the endless collection of ORMs
out there, etc. So I'm all in favour of it in principle so long as it can
be made to work reliably with limited performance impact on existing
workloads and without making life lots harder when adding new core
functionality, for extension authors etc. The same goes for built-in
pooling. I think PostgreSQL has needed some sort of separation of
"connection", "backend", "session" and "executor" for a long time and I'm
glad to see you working on it.
With that said: How do you intend to address the likelihood that this will
cause performance regressions for existing workloads that use temp tables
*without* relying on your session state and connection pooler? Consider
workloads that use temp tables for mid-long txns where txn pooling is
unimportant, where they also do plenty of read and write activity on
persistent tables. Classic OLAP/DW stuff. e.g.:
* four clients, four backends, four connections, session-level connections
that stay busy with minimal client sleeps
* All sessions run the same bench code
* transactions all read plenty of data from a medium to large persistent
table (think fact tables, etc)
* transactions store a filtered, joined dataset with some pre-computed
window results or something in temp tables
* benchmark workload makes big-ish temp tables to store intermediate data
for its medium-length transactions
* transactions also write to some persistent relations, say to record their
summarised results
How does it perform with and without your patch? I'm concerned that:
* the extra buffer locking and various IPC may degrade performance of temp
tables
* the temp table data in shared_buffers may put pressure on shared_buffers
space, cached pages for persistent tables all sessions are sharing;
* the temp table data in shared_buffers may put pressure on shared_buffers
space for dirty buffers, forcing writes of persistent tables out earlier
therefore reducing write-combining opportunities;
--
Craig Ringer http://www.2ndQuadrant.com/
2ndQuadrant - PostgreSQL Solutions for the Enterprise
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-11 06:52 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Craig Ringer <craig@2ndquadrant.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-08-11 06:52 UTC (permalink / raw)
To: Craig Ringer <craig@2ndquadrant.com>; +Cc: pgsql-hackers
On 10.08.2019 5:12, Craig Ringer wrote:
> On Fri, 9 Aug 2019 at 22:07, Konstantin Knizhnik
> <k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> wrote:
>
>
>
> Ok, here it is: global_private_temp-1.patch
>
>
> Fantastic.
>
> I'll put that high on my queue.
>
> I'd love to see something like this get in.
>
> Doubly so if it brings us closer to being able to use temp tables on
> physical read replicas, though I know there are plenty of other
> barriers there (not least of which being temp tables using persistent
> txns not vtxids)
>
> Does it have a CF entry?
https://commitfest.postgresql.org/24/2233/
> Also I have attached updated version of the global temp tables
> with shared buffers - global_shared_temp-1.patch
>
>
> Nice to see that split out. In addition to giving the first patch more
> hope of being committed this time around, it'll help with readability
> and testability too.
>
> To be clear, I have long wanted to see PostgreSQL have the "session"
> state abstraction you have implemented. I think it's really important
> for high client count OLTP workloads, working with the endless
> collection of ORMs out there, etc. So I'm all in favour of it in
> principle so long as it can be made to work reliably with limited
> performance impact on existing workloads and without making life lots
> harder when adding new core functionality, for extension authors etc.
> The same goes for built-in pooling. I think PostgreSQL has needed some
> sort of separation of "connection", "backend", "session" and
> "executor" for a long time and I'm glad to see you working on it.
>
> With that said: How do you intend to address the likelihood that this
> will cause performance regressions for existing workloads that use
> temp tables *without* relying on your session state and connection
> pooler? Consider workloads that use temp tables for mid-long txns
> where txn pooling is unimportant, where they also do plenty of read
> and write activity on persistent tables. Classic OLAP/DW stuff. e.g.:
>
> * four clients, four backends, four connections, session-level
> connections that stay busy with minimal client sleeps
> * All sessions run the same bench code
> * transactions all read plenty of data from a medium to large
> persistent table (think fact tables, etc)
> * transactions store a filtered, joined dataset with some pre-computed
> window results or something in temp tables
> * benchmark workload makes big-ish temp tables to store intermediate
> data for its medium-length transactions
> * transactions also write to some persistent relations, say to record
> their summarised results
>
> How does it perform with and without your patch? I'm concerned that:
>
> * the extra buffer locking and various IPC may degrade performance of
> temp tables
> * the temp table data in shared_buffers may put pressure on
> shared_buffers space, cached pages for persistent tables all sessions
> are sharing;
> * the temp table data in shared_buffers may put pressure on
> shared_buffers space for dirty buffers, forcing writes of persistent
> tables out earlier therefore reducing write-combining opportunities;
>
I agree that access to local buffers is cheaper than to shared buffers
because there is no lock overhead.
And the fact that access to local tables can not affect cached data of
persistent tables is also important.
But most of Postgres tables are still normal (persistent) tables access
through shared buffers.
And huge amount of efforts were made to make this access as efficient as
possible (use clock algorithm which doesn't require global lock,
atomic operations,...). Also using the same replacement discipline for
all tables at some workloads may be also preferable.
So it is not so obvious to me that in the described scenario local
buffer cache for temporary table really will provide significant advantages.
It will be interesting to perform some benchmarking - I am going to do it.
What I have observed right now is that in type scenario: dumping results
of huge query to temporary table with subsequent traverse of this table
old (local) temporary tables provide better performance (may be because
of small size of local buffer cache and different eviction policy).
But subsequent accesses to global shared table are faster (because it
completely fits in large shared buffer cache).
There is one more problem with global temporary tables for which I do
not know good solution now: collecting statistic.
As far as each backend has its own data, generally them may need
different query plans.
Right now if you perform "analyze table" in one backend, then it will
affect plans in all backends.
It can be considered not as bug, but as feature if we assume that
distribution if data in all backens is similar.
But if this assumption is not true, then it can be a problem.
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-11 07:14 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2019-08-11 07:14 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
Hi
> There is one more problem with global temporary tables for which I do not
> know good solution now: collecting statistic.
> As far as each backend has its own data, generally them may need different
> query plans.
> Right now if you perform "analyze table" in one backend, then it will
> affect plans in all backends.
> It can be considered not as bug, but as feature if we assume that
> distribution if data in all backens is similar.
> But if this assumption is not true, then it can be a problem.
>
Last point is probably the most difficult issue and I think about it years.
I have a experience with my customers so 99% of usage temp tables is
without statistics - just with good information only about rows. Only few
customers know so manual ANALYZE is necessary for temp tables (when it is
really necessary).
Sharing meta data about global temporary tables can real problem - probably
not about statistics, but surely about number of pages and number of rows.
There are two requirements:
a) we need some special meta data for any instance (per session) of global
temporary table (row, pages, statistics, maybe multicolumn statistics, ...)
b) we would not to use persistent global catalogue (against catalogue
bloating)
I see two possible solution:
1. hold these data only in memory in special buffers
2. hold these data in global temporary tables - it is similar to normal
tables - we can use global temp tables for metadata like classic persistent
tables are used for metadata of classic persistent tables. Next syscache
can be enhanced to work with union of two system tables.
I prefer @2 because changes can be implemented on deeper level.
Sharing metadata for global temp tables (current state if I understand
well) is good enough for develop stage, but It is hard to expect so it can
work generally in production environment.
Regards
p.s. I am very happy so you are working on this topic. It is interesting
and important problem.
Pavel
>
>
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-12 16:19 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-08-12 16:19 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
Hi,
On 11.08.2019 10:14, Pavel Stehule wrote:
>
> Hi
>
>
> There is one more problem with global temporary tables for which I
> do not know good solution now: collecting statistic.
> As far as each backend has its own data, generally them may need
> different query plans.
> Right now if you perform "analyze table" in one backend, then it
> will affect plans in all backends.
> It can be considered not as bug, but as feature if we assume that
> distribution if data in all backens is similar.
> But if this assumption is not true, then it can be a problem.
>
>
> Last point is probably the most difficult issue and I think about it
> years.
>
> I have a experience with my customers so 99% of usage temp tables is
> without statistics - just with good information only about rows. Only
> few customers know so manual ANALYZE is necessary for temp tables
> (when it is really necessary).
>
> Sharing meta data about global temporary tables can real problem -
> probably not about statistics, but surely about number of pages and
> number of rows.
But Postgres is not storing this information now anywhere else except
statistic, isn't it?
There was proposal to cache relation size, but it is not implemented
yet. If such cache exists, then we can use it to store local information
about global temporary tables.
So if 99% of users do not perform analyze for temporary tables, then
them will not be faced with this problem, right?
>
> There are two requirements:
>
> a) we need some special meta data for any instance (per session) of
> global temporary table (row, pages, statistics, maybe multicolumn
> statistics, ...)
>
> b) we would not to use persistent global catalogue (against catalogue
> bloating)
>
> I see two possible solution:
>
> 1. hold these data only in memory in special buffers
>
> 2. hold these data in global temporary tables - it is similar to
> normal tables - we can use global temp tables for metadata like
> classic persistent tables are used for metadata of classic persistent
> tables. Next syscache can be enhanced to work with union of two system
> tables.
>
> I prefer @2 because changes can be implemented on deeper level.
>
> Sharing metadata for global temp tables (current state if I understand
> well) is good enough for develop stage, but It is hard to expect so it
> can work generally in production environment.
>
I think that it not possible to assume that temporary data will aways
fir in memory.
So 1) seems to be not acceptable solution.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-12 16:47 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2019-08-12 16:47 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
po 12. 8. 2019 v 18:19 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:
> Hi,
>
> On 11.08.2019 10:14, Pavel Stehule wrote:
>
>
> Hi
>
>
>> There is one more problem with global temporary tables for which I do not
>> know good solution now: collecting statistic.
>> As far as each backend has its own data, generally them may need
>> different query plans.
>> Right now if you perform "analyze table" in one backend, then it will
>> affect plans in all backends.
>> It can be considered not as bug, but as feature if we assume that
>> distribution if data in all backens is similar.
>> But if this assumption is not true, then it can be a problem.
>>
>
> Last point is probably the most difficult issue and I think about it
> years.
>
> I have a experience with my customers so 99% of usage temp tables is
> without statistics - just with good information only about rows. Only few
> customers know so manual ANALYZE is necessary for temp tables (when it is
> really necessary).
>
> Sharing meta data about global temporary tables can real problem -
> probably not about statistics, but surely about number of pages and number
> of rows.
>
>
> But Postgres is not storing this information now anywhere else except
> statistic, isn't it?
>
not only - critical numbers are reltuples, relpages from pg_class
There was proposal to cache relation size, but it is not implemented yet.
> If such cache exists, then we can use it to store local information about
> global temporary tables.
> So if 99% of users do not perform analyze for temporary tables, then them
> will not be faced with this problem, right?
>
they use default statistics based on relpages. But for 1% of applications
statistics are critical - almost always for OLAP applications.
>
>
> There are two requirements:
>
> a) we need some special meta data for any instance (per session) of global
> temporary table (row, pages, statistics, maybe multicolumn statistics, ...)
>
> b) we would not to use persistent global catalogue (against catalogue
> bloating)
>
> I see two possible solution:
>
> 1. hold these data only in memory in special buffers
>
> 2. hold these data in global temporary tables - it is similar to normal
> tables - we can use global temp tables for metadata like classic persistent
> tables are used for metadata of classic persistent tables. Next syscache
> can be enhanced to work with union of two system tables.
>
> I prefer @2 because changes can be implemented on deeper level.
>
> Sharing metadata for global temp tables (current state if I understand
> well) is good enough for develop stage, but It is hard to expect so it can
> work generally in production environment.
>
>
> I think that it not possible to assume that temporary data will aways fir
> in memory.
> So 1) seems to be not acceptable solution.
>
I spoke only about metadata. Data should be stored in temp buffers (and
possibly in temp files)
Pavel
>
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-13 05:34 Craig Ringer <craig@2ndquadrant.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Craig Ringer @ 2019-08-13 05:34 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers
On Tue, 13 Aug 2019 at 00:47, Pavel Stehule <pavel.stehule@gmail.com> wrote:
> But Postgres is not storing this information now anywhere else except
>> statistic, isn't it?
>>
>
> not only - critical numbers are reltuples, relpages from pg_class
>
That's a very good point. relallvisible too. How's the global temp table
impl handling that right now, since you won't be changing the pg_class row?
AFAICS relpages doesn't need to be up to date (and reltuples certainly
doesn't) so presumably you're just leaving them as zero?
What happens right now if you ANALYZE or VACUUM ANALYZE a global temp
table? Is it just disallowed?
I'll need to check, but I wonder if periodically updating those fields in
pg_class impacts logical decoding too. Logical decoding must treat
transactions with catalog changes as special cases where it creates custom
snapshots and does other expensive additional work.
(See ReorderBufferXidSetCatalogChanges in reorderbuffer.c and its
callsites). We don't actually need to know relpages and reltuples during
logical decoding. It can probably ignore relfrozenxid and relminmxid
changes too, maybe even pg_statistic changes though I'd be less confident
about that one.
At some point I need to patch in a bunch of extra tracepoints and do some
perf tracing to see how often we do potentially unnecessary snapshot
related work in logical decoding.
> There was proposal to cache relation size, but it is not implemented yet.
>> If such cache exists, then we can use it to store local information about
>> global temporary tables.
>> So if 99% of users do not perform analyze for temporary tables, then them
>> will not be faced with this problem, right?
>>
>
> they use default statistics based on relpages. But for 1% of applications
> statistics are critical - almost always for OLAP applications.
>
Agreed. It's actually quite a common solution to user problem reports /
support queries about temp table performance: "Run ANALYZE. Consider
creating indexes too."
Which reminds me - if global temp tables do get added, it'll further
increase the desirability of exposing a feature to let users
disable+invalidate and then later reindex+enable indexes without icky
catalog hacking. So they can disable indexes for their local copy, load
data, re-enable indexes. That'd be "interesting" to implement for global
temp tables given that index state is part of the pg_index row associated
with an index rel though.
1. hold these data only in memory in special buffers
>>
>>
I don't see that working well for pg_statistic or anything else that holds
nontrivial user data though.
> 2. hold these data in global temporary tables - it is similar to normal
>> tables - we can use global temp tables for metadata like classic persistent
>> tables are used for metadata of classic persistent tables. Next syscache
>> can be enhanced to work with union of two system tables.
>>
>>
Very meta. Syscache and relcache are extremely performance critical but
could probably skip scans entirely on backends that haven't used any global
temp tables.
I don't know the relevant caches well enough to have a useful opinion here.
> I think that it not possible to assume that temporary data will aways fir
>> in memory.
>> So 1) seems to be not acceptable solution.
>>
>
It'd only be the metadata, but if it includes things like column histograms
and most frequent value data that'd still be undesirable to have pinned in
backend memory.
--
Craig Ringer http://www.2ndQuadrant.com/
2ndQuadrant - PostgreSQL Solutions for the Enterprise
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-13 08:19 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Craig Ringer <craig@2ndquadrant.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-08-13 08:19 UTC (permalink / raw)
To: Craig Ringer <craig@2ndquadrant.com>; Pavel Stehule <pavel.stehule@gmail.com>; +Cc: pgsql-hackers
On 13.08.2019 8:34, Craig Ringer wrote:
> On Tue, 13 Aug 2019 at 00:47, Pavel Stehule <pavel.stehule@gmail.com
> <mailto:pavel.stehule@gmail.com>> wrote:
>
> But Postgres is not storing this information now anywhere else
> except statistic, isn't it?
>
>
> not only - critical numbers are reltuples, relpages from pg_class
>
>
> That's a very good point. relallvisible too. How's the global temp
> table impl handling that right now, since you won't be changing the
> pg_class row? AFAICS relpages doesn't need to be up to date (and
> reltuples certainly doesn't) so presumably you're just leaving them as
> zero?
As far as I understand relpages and reltuples are set only when you
perform "analyze" of the table.
>
> What happens right now if you ANALYZE or VACUUM ANALYZE a global temp
> table? Is it just disallowed?
No, it is not disallowed now.
It updates the statistic and also fields in pg_class which are shared by
all backends.
So all backends will now build plans according to this statistic.
Certainly it may lead to not so efficient plans if there are large
differences in number of tuples stored in this table in different backends.
But seems to me critical mostly in case of presence of indexes for
temporary table. And it seems to me that users are created indexes for
temporary tables even rarely than doing analyze for them.
>
> I'll need to check, but I wonder if periodically updating those fields
> in pg_class impacts logical decoding too. Logical decoding must treat
> transactions with catalog changes as special cases where it creates
> custom snapshots and does other expensive additional work.
> (See ReorderBufferXidSetCatalogChanges in reorderbuffer.c and its
> callsites). We don't actually need to know relpages and reltuples
> during logical decoding. It can probably ignore relfrozenxid
> and relminmxid changes too, maybe even pg_statistic changes though I'd
> be less confident about that one.
>
> At some point I need to patch in a bunch of extra tracepoints and do
> some perf tracing to see how often we do potentially unnecessary
> snapshot related work in logical decoding.
Temporary tables (both local and global) as well as unlogged tables are
not subject of logical replication, aren't them?
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-13 08:21 Craig Ringer <craig@2ndquadrant.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
1 sibling, 1 reply; 525+ messages in thread
From: Craig Ringer @ 2019-08-13 08:21 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: pgsql-hackers
On Fri, 9 Aug 2019 at 22:07, Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
wrote:
>
>
> Ok, here it is: global_private_temp-1.patch
>
Initial pass review follows.
Relation name "SESSION" is odd. I guess you're avoiding "global" because
the data is session-scoped, not globally temporary. But I'm not sure
"session" fits either - after all regular temp tables are also session
temporary tables. I won't focus on naming further beyond asking that it be
consistent though, right now there's a mix of "global" in some places and
"session" in others.
Why do you need to do all this indirection with changing RelFileNode to
RelFileNodeBackend in the bufmgr, changing BufferGetTag etc? Similarly,
your changes of RelFileNodeBackendIsTemp to RelFileNodeBackendIsLocalTemp .
Did you look into my suggestion of extending the relmapper so that global
temp tables would have a relfilenode of 0 like pg_class etc, and use a
backend-local map of oid-to-relfilenode mappings? I'm guessing you did it
the way you did instead to lay the groundwork for cross-backend sharing,
but if so it should IMO be in that patch. Maybe my understanding of the
existing temp table mechanics is just insufficient as I
see RelFileNodeBackendIsTemp is already used in some aspects of existing
temp relation handling.
Similarly, TruncateSessionRelations probably shouldn't need to exist in
this patch in its current form; there's no shared_buffers use to clean and
the same file cleanup mechanism should handle both session-temp and
local-temp relfilenodes.
A number of places make a change like this:
rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+ rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION
and I'd like to see a test macro or inline static for it since it's
repeated so much. Mostly to make the intent clear: "is this a relation with
temporary backend-scoped data?"
This test:
+ if (blkno == BTREE_METAPAGE && PageIsNew(BufferGetPage(buf)) &&
IsSessionRelationBackendId(rel->rd_backend))
+ _bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
seems sensible but I'm wondering if there's a way to channel initialization
of global-temp objects through a bit more of a common path, so it reads
more obviously as a common test applying to all global-temp tables. "Global
temp table not initialized in session yet? Initialize it." So we don't have
to have every object type do an object type specific test for "am I
actually uninitialized?" in all paths it might hit. I guess I expected to
see something more like a
if (RelGlobalTempUninitialized(rel))
but maybe I've been doing too much Java ;)
A similar test reappears here for sequences:
+ if (rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION &&
PageIsNew(page))
Why is this written differently?
Sequence initialization ignores sequence startval/firstval settings. Why?
- else if (newrelpersistence == RELPERSISTENCE_PERMANENT)
+ else if (newrelpersistence != RELPERSISTENCE_TEMP)
Doesn't this change the test outcome for RELPERSISTENCE_UNLOGGED?
In PreCommit_on_commit_actions, in the the ONCOMMIT_DELETE_ROWS case, is
there any way to still respect the XACT_FLAGS_ACCESSEDTEMPNAMESPACE flag if
the oid is for a backend-temp table not a global-temp table?
+ bool isLocalBuf = SmgrIsTemp(smgr) && relpersistence ==
RELPERSISTENCE_TEMP;
Won't session-temp tables have local buffers too? Until your next patch
that adds shared_buffers storage for them anyway?
+ * These is no need to separate them at file system level, so just
subtract SessionRelFirstBackendId
+ * to avoid too long file names.
I agree that there's no reason to be able to differentiate between
local-temp and session-temp relfilenodes at the filesystem level.
> Also I have attached updated version of the global temp tables with shared
> buffers - global_shared_temp-1.patch
> It is certainly larger (~2k lines vs. 1.5k lines) because it is changing
> BufferTag and related functions.
> But I do not think that this different is so critical.
> Still have a wish to kill two birds with one stone:)
>
>
>
>
>
>
>
>
> --
>
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
>
--
Craig Ringer http://www.2ndQuadrant.com/
2ndQuadrant - PostgreSQL Solutions for the Enterprise
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-13 08:27 Craig Ringer <craig@2ndquadrant.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Craig Ringer @ 2019-08-13 08:27 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
On Tue, 13 Aug 2019 at 16:19, Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
wrote:
>
>
> On 13.08.2019 8:34, Craig Ringer wrote:
>
> On Tue, 13 Aug 2019 at 00:47, Pavel Stehule <pavel.stehule@gmail.com>
> wrote:
>
>
>> But Postgres is not storing this information now anywhere else except
>>> statistic, isn't it?
>>>
>>
>> not only - critical numbers are reltuples, relpages from pg_class
>>
>
> That's a very good point. relallvisible too. How's the global temp table
> impl handling that right now, since you won't be changing the pg_class row?
> AFAICS relpages doesn't need to be up to date (and reltuples certainly
> doesn't) so presumably you're just leaving them as zero?
>
> As far as I understand relpages and reltuples are set only when you
> perform "analyze" of the table.
>
Also autovacuum's autoanalyze.
What happens right now if you ANALYZE or VACUUM ANALYZE a global temp
> table? Is it just disallowed?
>
>
> No, it is not disallowed now.
> It updates the statistic and also fields in pg_class which are shared by
> all backends.
> So all backends will now build plans according to this statistic.
> Certainly it may lead to not so efficient plans if there are large
> differences in number of tuples stored in this table in different backends.
> But seems to me critical mostly in case of presence of indexes for
> temporary table. And it seems to me that users are created indexes for
> temporary tables even rarely than doing analyze for them.
>
That doesn't seem too bad TBH. Hacky but it doesn't seem dangerously wrong
and as likely to be helpful as not if clearly documented.
> Temporary tables (both local and global) as well as unlogged tables are
> not subject of logical replication, aren't them?
>
>
Right. But in the same way that they're still present in the catalogs, I
think they still affect catalog snapshots maintained by logical decoding's
historic snapshot manager as temp table creation/drop will still AFAIK
cause catalog invalidations to be written on commit. I need to double check
that.
--
Craig Ringer http://www.2ndQuadrant.com/
2ndQuadrant - PostgreSQL Solutions for the Enterprise
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-13 09:20 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Craig Ringer <craig@2ndquadrant.com>
0 siblings, 0 replies; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-08-13 09:20 UTC (permalink / raw)
To: Craig Ringer <craig@2ndquadrant.com>; +Cc: pgsql-hackers
On 13.08.2019 11:21, Craig Ringer wrote:
> On Fri, 9 Aug 2019 at 22:07, Konstantin Knizhnik
> <k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> wrote:
>
>
>
> Ok, here it is: global_private_temp-1.patch
>
>
>
> Initial pass review follows.
>
> Relation name "SESSION" is odd. I guess you're avoiding "global"
> because the data is session-scoped, not globally temporary. But I'm
> not sure "session" fits either - after all regular temp tables are
> also session temporary tables. I won't focus on naming further beyond
> asking that it be consistent though, right now there's a mix of
> "global" in some places and "session" in others.
>
I have supported both forms "create session table" and "create global temp".
Both "session" and "global" are expected PostgreSQL keywords so we do
not need to introduce new one (unlike "public" or "shared").
The form "global temp" is used in Oracle so it seems to be natural to
provide similar syntax.
I am not insisting on this syntax and will consider all other suggestions.
But IMHO almost any SQL keyword is overloaded and have different meaning.
Temporary tables has session as living area (or transaction if created
with "ON COMMIT DROP" clause).
So calling them "session tables" is actually more clear than just
"temporary tables".
But "local temp tables" can be also considered as session tables. So may
be it is really not so good idea
to use "session" keyword for them.
>
> Why do you need to do all this indirection with changing RelFileNode
> to RelFileNodeBackend in the bufmgr, changing BufferGetTag etc?
> Similarly, your changes of RelFileNodeBackendIsTemp
> to RelFileNodeBackendIsLocalTemp . Did you look into my suggestion of
> extending the relmapper so that global temp tables would have a
> relfilenode of 0 like pg_class etc, and use a backend-local map of
> oid-to-relfilenode mappings? I'm guessing you did it the way you did
> instead to lay the groundwork for cross-backend sharing, but if so it
> should IMO be in that patch. Maybe my understanding of the existing
> temp table mechanics is just insufficient as I
> see RelFileNodeBackendIsTemp is already used in some aspects of
> existing temp relation handling.
Sorry, are you really speaking about global_private_temp-1.patch?
This patch doesn't change bufmgr file at all.
May be you looked at another patch - global_shared_temp-1.patch
which is accessing shared tables though shared buffers and so have to
change buffer tag to include backend ID in it.
>
> Similarly, TruncateSessionRelations probably shouldn't need to exist
> in this patch in its current form; there's no shared_buffers use to
> clean and the same file cleanup mechanism should handle both
> session-temp and local-temp relfilenodes.
>
In global_private_temp-1.patch TruncateSessionRelations does nothing
with shared buffers, it just delete relation files.
>
> A number of places make a change like this:
> rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
> + rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION
>
> and I'd like to see a test macro or inline static for it since it's
> repeated so much. Mostly to make the intent clear: "is this a relation
> with temporary backend-scoped data?"
>
I consider to call such macro IsSessionRelation() or something like this
but you do not like notion "session".
Is IsBackendScopedRelation() a better choice?
>
> This test:
>
> + if (blkno == BTREE_METAPAGE && PageIsNew(BufferGetPage(buf)) &&
> IsSessionRelationBackendId(rel->rd_backend))
> + _bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
>
> seems sensible but I'm wondering if there's a way to channel
> initialization of global-temp objects through a bit more of a common
> path, so it reads more obviously as a common test applying to all
> global-temp tables. "Global temp table not initialized in session yet?
> Initialize it." So we don't have to have every object type do an
> object type specific test for "am I actually uninitialized?" in all
> paths it might hit. I guess I expected to see something more like a
>
> if (RelGlobalTempUninitialized(rel))
>
> but maybe I've been doing too much Java ;)
>
> A similar test reappears here for sequences:
>
> + if (rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION &&
> PageIsNew(page))
>
> Why is this written differently?
>
>
Just because I wrote them in different moment of times:)
I think that adding RelGlobalTempUninitialized is really good idea.
> Sequence initialization ignores sequence startval/firstval settings. Why?
>
>
I am handling only case of implicitly created sequences for
SERIAL/BIGSERIAL columns.
Is it possible to explicitly specify initial value and step for them?
If so, this place should definitely be rewritten.
> - else if (newrelpersistence == RELPERSISTENCE_PERMANENT)
> + else if (newrelpersistence != RELPERSISTENCE_TEMP)
>
> Doesn't this change the test outcome for RELPERSISTENCE_UNLOGGED?
>
RELPERSISTENCE_UNLOGGED case is handle in previous IF branch.
>
> In PreCommit_on_commit_actions, in the the ONCOMMIT_DELETE_ROWS case,
> is there any way to still respect the XACT_FLAGS_ACCESSEDTEMPNAMESPACE
> flag if the oid is for a backend-temp table not a global-temp table?
>
If it is local temp table, then XACT_FLAGS_ACCESSEDTEMPNAMESPACE is set
and so there is no need to check this flag.
And as far as XACT_FLAGS_ACCESSEDTEMPNAMESPACE is not set now for
global temp tables, I have to remove this check.
So for local temp table original behavior is preserved.
The question is why I do not set XACT_FLAGS_ACCESSEDTEMPNAMESPACE for
global temp tables?
I wanted to avoid current limitation for using temp tables in prepared
transactions.
Global metadata allows to eliminate some problems related with using
temp tables in 2PC.
But I am not sure that it eliminates ALL problems and there are no
strange effects related with
prepared transactions&global temp tables.
>
> + bool isLocalBuf = SmgrIsTemp(smgr) && relpersistence ==
> RELPERSISTENCE_TEMP;
>
> Won't session-temp tables have local buffers too? Until your next
> patch that adds shared_buffers storage for them anyway?
>
Once again, it is change from global_shared_temp-1.patch, not from
global_private_temp-1.patch
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-13 13:50 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Craig Ringer <craig@2ndquadrant.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-08-13 13:50 UTC (permalink / raw)
To: Craig Ringer <craig@2ndquadrant.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
On 13.08.2019 11:27, Craig Ringer wrote:
>
>
> On Tue, 13 Aug 2019 at 16:19, Konstantin Knizhnik
> <k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> wrote:
>
>
>
> On 13.08.2019 8:34, Craig Ringer wrote:
>> On Tue, 13 Aug 2019 at 00:47, Pavel Stehule
>> <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> wrote:
>>
>> But Postgres is not storing this information now anywhere
>> else except statistic, isn't it?
>>
>>
>> not only - critical numbers are reltuples, relpages from pg_class
>>
>>
>> That's a very good point. relallvisible too. How's the global
>> temp table impl handling that right now, since you won't be
>> changing the pg_class row? AFAICS relpages doesn't need to be up
>> to date (and reltuples certainly doesn't) so presumably you're
>> just leaving them as zero?
> As far as I understand relpages and reltuples are set only when
> you perform "analyze" of the table.
>
>
> Also autovacuum's autoanalyze.
When it happen?
I have created normal table, populated it with some data and then wait
several hours but pg_class was not updated for this table.
I attach to this mail slightly refactored versions of this patches with
fixes of issues reported in your review.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
[text/x-patch] global_shared_temp-2.patch (73.8K, ../../91de2094-165a-04f1-9b3d-348473394345@postgrespro.ru/3-global_shared_temp-2.patch)
download | inline diff:
diff --git a/contrib/pg_buffercache/pg_buffercache_pages.c b/contrib/pg_buffercache/pg_buffercache_pages.c
index 1bd579f..2d93f6f 100644
--- a/contrib/pg_buffercache/pg_buffercache_pages.c
+++ b/contrib/pg_buffercache/pg_buffercache_pages.c
@@ -153,9 +153,9 @@ pg_buffercache_pages(PG_FUNCTION_ARGS)
buf_state = LockBufHdr(bufHdr);
fctx->record[i].bufferid = BufferDescriptorGetBuffer(bufHdr);
- fctx->record[i].relfilenode = bufHdr->tag.rnode.relNode;
- fctx->record[i].reltablespace = bufHdr->tag.rnode.spcNode;
- fctx->record[i].reldatabase = bufHdr->tag.rnode.dbNode;
+ fctx->record[i].relfilenode = bufHdr->tag.rnode.node.relNode;
+ fctx->record[i].reltablespace = bufHdr->tag.rnode.node.spcNode;
+ fctx->record[i].reldatabase = bufHdr->tag.rnode.node.dbNode;
fctx->record[i].forknum = bufHdr->tag.forkNum;
fctx->record[i].blocknum = bufHdr->tag.blockNum;
fctx->record[i].usagecount = BUF_STATE_GET_USAGECOUNT(buf_state);
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 38ae240..8a04954 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -608,9 +608,9 @@ apw_dump_now(bool is_bgworker, bool dump_unlogged)
if (buf_state & BM_TAG_VALID &&
((buf_state & BM_PERMANENT) || dump_unlogged))
{
- block_info_array[num_blocks].database = bufHdr->tag.rnode.dbNode;
- block_info_array[num_blocks].tablespace = bufHdr->tag.rnode.spcNode;
- block_info_array[num_blocks].filenode = bufHdr->tag.rnode.relNode;
+ block_info_array[num_blocks].database = bufHdr->tag.rnode.node.dbNode;
+ block_info_array[num_blocks].tablespace = bufHdr->tag.rnode.node.spcNode;
+ block_info_array[num_blocks].filenode = bufHdr->tag.rnode.node.relNode;
block_info_array[num_blocks].forknum = bufHdr->tag.forkNum;
block_info_array[num_blocks].blocknum = bufHdr->tag.blockNum;
++num_blocks;
diff --git a/src/backend/access/gin/ginxlog.c b/src/backend/access/gin/ginxlog.c
index c945b28..14d4e48 100644
--- a/src/backend/access/gin/ginxlog.c
+++ b/src/backend/access/gin/ginxlog.c
@@ -95,13 +95,13 @@ ginRedoInsertEntry(Buffer buffer, bool isLeaf, BlockNumber rightblkno, void *rda
if (PageAddItem(page, (Item) itup, IndexTupleSize(itup), offset, false, false) == InvalidOffsetNumber)
{
- RelFileNode node;
+ RelFileNodeBackend rnode;
ForkNumber forknum;
BlockNumber blknum;
- BufferGetTag(buffer, &node, &forknum, &blknum);
+ BufferGetTag(buffer, &rnode, &forknum, &blknum);
elog(ERROR, "failed to add item to index page in %u/%u/%u",
- node.spcNode, node.dbNode, node.relNode);
+ rnode.node.spcNode, rnode.node.dbNode, rnode.node.relNode);
}
}
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 9726020..c99701d 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/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 09bc6fe..c60effd 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -671,6 +671,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 5962126..bdb6c95 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/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index 3ec67d4..edec8ca 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -213,6 +213,7 @@ void
XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
{
registered_buffer *regbuf;
+ RelFileNodeBackend rnode;
/* NO_IMAGE doesn't make sense with FORCE_IMAGE */
Assert(!((flags & REGBUF_FORCE_IMAGE) && (flags & (REGBUF_NO_IMAGE))));
@@ -227,7 +228,8 @@ XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
regbuf = ®istered_buffers[block_id];
- BufferGetTag(buffer, ®buf->rnode, ®buf->forkno, ®buf->block);
+ BufferGetTag(buffer, &rnode, ®buf->forkno, ®buf->block);
+ regbuf->rnode = rnode.node;
regbuf->page = BufferGetPage(buffer);
regbuf->flags = flags;
regbuf->rdata_tail = (XLogRecData *) ®buf->rdata_head;
@@ -919,7 +921,7 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std)
int flags;
PGAlignedBlock copied_buffer;
char *origdata = (char *) BufferGetBlock(buffer);
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forkno;
BlockNumber blkno;
@@ -948,7 +950,7 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std)
flags |= REGBUF_STANDARD;
BufferGetTag(buffer, &rnode, &forkno, &blkno);
- XLogRegisterBlock(0, &rnode, forkno, blkno, copied_buffer.data, flags);
+ XLogRegisterBlock(0, &rnode.node, forkno, blkno, copied_buffer.data, flags);
recptr = XLogInsert(RM_XLOG_ID, XLOG_FPI_FOR_HINT);
}
@@ -1009,7 +1011,7 @@ XLogRecPtr
log_newpage_buffer(Buffer buffer, bool page_std)
{
Page page = BufferGetPage(buffer);
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forkNum;
BlockNumber blkno;
@@ -1018,7 +1020,7 @@ log_newpage_buffer(Buffer buffer, bool page_std)
BufferGetTag(buffer, &rnode, &forkNum, &blkno);
- return log_newpage(&rnode, forkNum, blkno, page, page_std);
+ return log_newpage(&rnode.node, forkNum, blkno, page, page_std);
}
/*
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index a065419..8814afb 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -409,6 +409,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 99ae159..24b2438 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3612,7 +3612,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 3cc886f..a111ddc 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 cedb4ee..d11c5b3 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 0960b33..6c3998f 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 fb2be10..a31f775 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -586,7 +586,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);
@@ -7678,6 +7679,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,
@@ -14082,6 +14089,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 */
@@ -14569,14 +14583,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 c97bb36..f9b2000 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3265,20 +3265,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 a9b2f8b..2f261b9 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 073f313..3383c35 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2069,7 +2069,7 @@ do_autovacuum(void)
* Check if it is a temp table (presumably, of some other backend's).
* We cannot safely process other backends' temp tables.
*/
- if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+ if (IsLocalRelpersistence(classForm->relpersistence))
{
/*
* We just ignore it if the owning backend is still active and
@@ -2154,7 +2154,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/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index e8ffa04..2004d2f 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -3483,6 +3483,7 @@ ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
{
ReorderBufferTupleCidKey key;
ReorderBufferTupleCidEnt *ent;
+ RelFileNodeBackend rnode;
ForkNumber forkno;
BlockNumber blockno;
bool updated_mapping = false;
@@ -3496,7 +3497,8 @@ ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
* get relfilenode from the buffer, no convenient way to access it other
* than that.
*/
- BufferGetTag(buffer, &key.relnode, &forkno, &blockno);
+ BufferGetTag(buffer, &rnode, &forkno, &blockno);
+ key.relnode = rnode.node;
/* tuples can only be in the main fork */
Assert(forkno == MAIN_FORKNUM);
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 6f3a402..76ce953 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -556,7 +556,7 @@ PrefetchBuffer(Relation reln, ForkNumber forkNum, BlockNumber blockNum)
int buf_id;
/* create a tag so we can lookup the buffer */
- INIT_BUFFERTAG(newTag, reln->rd_smgr->smgr_rnode.node,
+ INIT_BUFFERTAG(newTag, reln->rd_smgr->smgr_rnode,
forkNum, blockNum);
/* determine its hash code and partition lock ID */
@@ -710,7 +710,7 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
Block bufBlock;
bool found;
bool isExtend;
- bool isLocalBuf = SmgrIsTemp(smgr);
+ bool isLocalBuf = SmgrIsTemp(smgr) && relpersistence == RELPERSISTENCE_TEMP;
*hit = false;
@@ -1010,7 +1010,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
uint32 buf_state;
/* create a tag so we can lookup the buffer */
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* determine its hash code and partition lock ID */
newHash = BufTableHashCode(&newTag);
@@ -1532,7 +1532,8 @@ ReleaseAndReadBuffer(Buffer buffer,
{
bufHdr = GetLocalBufferDescriptor(-buffer - 1);
if (bufHdr->tag.blockNum == blockNum &&
- RelFileNodeEquals(bufHdr->tag.rnode, relation->rd_node) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, relation->rd_node) &&
+ bufHdr->tag.rnode.backend == relation->rd_backend &&
bufHdr->tag.forkNum == forkNum)
return buffer;
ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
@@ -1543,7 +1544,8 @@ ReleaseAndReadBuffer(Buffer buffer,
bufHdr = GetBufferDescriptor(buffer - 1);
/* we have pin, so it's ok to examine tag without spinlock */
if (bufHdr->tag.blockNum == blockNum &&
- RelFileNodeEquals(bufHdr->tag.rnode, relation->rd_node) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, relation->rd_node) &&
+ bufHdr->tag.rnode.backend == relation->rd_backend &&
bufHdr->tag.forkNum == forkNum)
return buffer;
UnpinBuffer(bufHdr, true);
@@ -1845,8 +1847,8 @@ BufferSync(int flags)
item = &CkptBufferIds[num_to_scan++];
item->buf_id = buf_id;
- item->tsId = bufHdr->tag.rnode.spcNode;
- item->relNode = bufHdr->tag.rnode.relNode;
+ item->tsId = bufHdr->tag.rnode.node.spcNode;
+ item->relNode = bufHdr->tag.rnode.node.relNode;
item->forkNum = bufHdr->tag.forkNum;
item->blockNum = bufHdr->tag.blockNum;
}
@@ -2559,7 +2561,7 @@ PrintBufferLeakWarning(Buffer buffer)
}
/* theoretically we should lock the bufhdr here */
- path = relpathbackend(buf->tag.rnode, backend, buf->tag.forkNum);
+ path = relpathbackend(buf->tag.rnode.node, backend, buf->tag.forkNum);
buf_state = pg_atomic_read_u32(&buf->state);
elog(WARNING,
"buffer refcount leak: [%03d] "
@@ -2631,7 +2633,7 @@ BufferGetBlockNumber(Buffer buffer)
* a buffer.
*/
void
-BufferGetTag(Buffer buffer, RelFileNode *rnode, ForkNumber *forknum,
+BufferGetTag(Buffer buffer, RelFileNodeBackend *rnode, ForkNumber *forknum,
BlockNumber *blknum)
{
BufferDesc *bufHdr;
@@ -2696,7 +2698,7 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
/* Find smgr relation for buffer */
if (reln == NULL)
- reln = smgropen(buf->tag.rnode, InvalidBackendId);
+ reln = smgropen(buf->tag.rnode.node, buf->tag.rnode.backend);
TRACE_POSTGRESQL_BUFFER_FLUSH_START(buf->tag.forkNum,
buf->tag.blockNum,
@@ -2930,7 +2932,7 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber forkNum,
int i;
/* If it's a local relation, it's localbuf.c's problem. */
- if (RelFileNodeBackendIsTemp(rnode))
+ if (RelFileNodeBackendIsLocalTemp(rnode))
{
if (rnode.backend == MyBackendId)
DropRelFileNodeLocalBuffers(rnode.node, forkNum, firstDelBlock);
@@ -2958,11 +2960,11 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber forkNum,
* We could check forkNum and blockNum as well as the rnode, but the
* incremental win from doing so seems small.
*/
- if (!RelFileNodeEquals(bufHdr->tag.rnode, rnode.node))
+ if (!RelFileNodeBackendEquals(bufHdr->tag.rnode, rnode))
continue;
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rnode.node) &&
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, rnode) &&
bufHdr->tag.forkNum == forkNum &&
bufHdr->tag.blockNum >= firstDelBlock)
InvalidateBuffer(bufHdr); /* releases spinlock */
@@ -2985,24 +2987,24 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
{
int i,
n = 0;
- RelFileNode *nodes;
+ RelFileNodeBackend *nodes;
bool use_bsearch;
if (nnodes == 0)
return;
- nodes = palloc(sizeof(RelFileNode) * nnodes); /* non-local relations */
+ nodes = palloc(sizeof(RelFileNodeBackend) * nnodes); /* non-local relations */
/* If it's a local relation, it's localbuf.c's problem. */
for (i = 0; i < nnodes; i++)
{
- if (RelFileNodeBackendIsTemp(rnodes[i]))
+ if (RelFileNodeBackendIsLocalTemp(rnodes[i]))
{
if (rnodes[i].backend == MyBackendId)
DropRelFileNodeAllLocalBuffers(rnodes[i].node);
}
else
- nodes[n++] = rnodes[i].node;
+ nodes[n++] = rnodes[i];
}
/*
@@ -3025,11 +3027,11 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
/* sort the list of rnodes if necessary */
if (use_bsearch)
- pg_qsort(nodes, n, sizeof(RelFileNode), rnode_comparator);
+ pg_qsort(nodes, n, sizeof(RelFileNodeBackend), rnode_comparator);
for (i = 0; i < NBuffers; i++)
{
- RelFileNode *rnode = NULL;
+ RelFileNodeBackend *rnode = NULL;
BufferDesc *bufHdr = GetBufferDescriptor(i);
uint32 buf_state;
@@ -3044,7 +3046,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
for (j = 0; j < n; j++)
{
- if (RelFileNodeEquals(bufHdr->tag.rnode, nodes[j]))
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, nodes[j]))
{
rnode = &nodes[j];
break;
@@ -3054,7 +3056,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
else
{
rnode = bsearch((const void *) &(bufHdr->tag.rnode),
- nodes, n, sizeof(RelFileNode),
+ nodes, n, sizeof(RelFileNodeBackend),
rnode_comparator);
}
@@ -3063,7 +3065,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
continue;
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, (*rnode)))
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, (*rnode)))
InvalidateBuffer(bufHdr); /* releases spinlock */
else
UnlockBufHdr(bufHdr, buf_state);
@@ -3102,11 +3104,11 @@ DropDatabaseBuffers(Oid dbid)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (bufHdr->tag.rnode.dbNode != dbid)
+ if (bufHdr->tag.rnode.node.dbNode != dbid)
continue;
buf_state = LockBufHdr(bufHdr);
- if (bufHdr->tag.rnode.dbNode == dbid)
+ if (bufHdr->tag.rnode.node.dbNode == dbid)
InvalidateBuffer(bufHdr); /* releases spinlock */
else
UnlockBufHdr(bufHdr, buf_state);
@@ -3136,7 +3138,7 @@ PrintBufferDescs(void)
"[%02d] (freeNext=%d, rel=%s, "
"blockNum=%u, flags=0x%x, refcount=%u %d)",
i, buf->freeNext,
- relpathbackend(buf->tag.rnode, InvalidBackendId, buf->tag.forkNum),
+ relpath(buf->tag.rnode, buf->tag.forkNum),
buf->tag.blockNum, buf->flags,
buf->refcount, GetPrivateRefCount(b));
}
@@ -3204,7 +3206,8 @@ FlushRelationBuffers(Relation rel)
uint32 buf_state;
bufHdr = GetLocalBufferDescriptor(i);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+ if (RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node) &&
+ bufHdr->tag.rnode.backend == rel->rd_backend &&
((buf_state = pg_atomic_read_u32(&bufHdr->state)) &
(BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
@@ -3251,13 +3254,15 @@ FlushRelationBuffers(Relation rel)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
+ if (!RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node)
+ || bufHdr->tag.rnode.backend != rel->rd_backend)
continue;
ReservePrivateRefCountEntry();
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+ if (RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node) &&
+ bufHdr->tag.rnode.backend == rel->rd_backend &&
(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
PinBuffer_Locked(bufHdr);
@@ -3305,13 +3310,13 @@ FlushDatabaseBuffers(Oid dbid)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (bufHdr->tag.rnode.dbNode != dbid)
+ if (bufHdr->tag.rnode.node.dbNode != dbid)
continue;
ReservePrivateRefCountEntry();
buf_state = LockBufHdr(bufHdr);
- if (bufHdr->tag.rnode.dbNode == dbid &&
+ if (bufHdr->tag.rnode.node.dbNode == dbid &&
(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
PinBuffer_Locked(bufHdr);
@@ -4051,7 +4056,7 @@ AbortBufferIO(void)
/* Buffer is pinned, so we can read tag without spinlock */
char *path;
- path = relpathperm(buf->tag.rnode, buf->tag.forkNum);
+ path = relpath(buf->tag.rnode, buf->tag.forkNum);
ereport(WARNING,
(errcode(ERRCODE_IO_ERROR),
errmsg("could not write block %u of %s",
@@ -4075,7 +4080,7 @@ shared_buffer_write_error_callback(void *arg)
/* Buffer is pinned, so we can read the tag without locking the spinlock */
if (bufHdr != NULL)
{
- char *path = relpathperm(bufHdr->tag.rnode, bufHdr->tag.forkNum);
+ char *path = relpath(bufHdr->tag.rnode, bufHdr->tag.forkNum);
errcontext("writing block %u of relation %s",
bufHdr->tag.blockNum, path);
@@ -4093,7 +4098,7 @@ local_buffer_write_error_callback(void *arg)
if (bufHdr != NULL)
{
- char *path = relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ char *path = relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum);
errcontext("writing block %u of relation %s",
@@ -4108,22 +4113,27 @@ local_buffer_write_error_callback(void *arg)
static int
rnode_comparator(const void *p1, const void *p2)
{
- RelFileNode n1 = *(const RelFileNode *) p1;
- RelFileNode n2 = *(const RelFileNode *) p2;
+ RelFileNodeBackend n1 = *(const RelFileNodeBackend *) p1;
+ RelFileNodeBackend n2 = *(const RelFileNodeBackend *) p2;
- if (n1.relNode < n2.relNode)
+ if (n1.node.relNode < n2.node.relNode)
return -1;
- else if (n1.relNode > n2.relNode)
+ else if (n1.node.relNode > n2.node.relNode)
return 1;
- if (n1.dbNode < n2.dbNode)
+ if (n1.node.dbNode < n2.node.dbNode)
return -1;
- else if (n1.dbNode > n2.dbNode)
+ else if (n1.node.dbNode > n2.node.dbNode)
return 1;
- if (n1.spcNode < n2.spcNode)
+ if (n1.node.spcNode < n2.node.spcNode)
return -1;
- else if (n1.spcNode > n2.spcNode)
+ else if (n1.node.spcNode > n2.node.spcNode)
+ return 1;
+
+ if (n1.backend < n2.backend)
+ return -1;
+ else if (n1.backend > n2.backend)
return 1;
else
return 0;
@@ -4359,7 +4369,7 @@ IssuePendingWritebacks(WritebackContext *context)
next = &context->pending_writebacks[i + ahead + 1];
/* different file, stop */
- if (!RelFileNodeEquals(cur->tag.rnode, next->tag.rnode) ||
+ if (!RelFileNodeBackendEquals(cur->tag.rnode, next->tag.rnode) ||
cur->tag.forkNum != next->tag.forkNum)
break;
@@ -4378,7 +4388,7 @@ IssuePendingWritebacks(WritebackContext *context)
i += ahead;
/* and finally tell the kernel to write the data to storage */
- reln = smgropen(tag.rnode, InvalidBackendId);
+ reln = smgropen(tag.rnode.node, tag.rnode.backend);
smgrwriteback(reln, tag.forkNum, tag.blockNum, nblocks);
}
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index f5f6a29..6bd5ecb 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -68,7 +68,7 @@ LocalPrefetchBuffer(SMgrRelation smgr, ForkNumber forkNum,
BufferTag newTag; /* identity of requested block */
LocalBufferLookupEnt *hresult;
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* Initialize local buffers if first request in this session */
if (LocalBufHash == NULL)
@@ -111,7 +111,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
bool found;
uint32 buf_state;
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* Initialize local buffers if first request in this session */
if (LocalBufHash == NULL)
@@ -209,7 +209,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
Page localpage = (char *) LocalBufHdrGetBlock(bufHdr);
/* Find smgr relation for buffer */
- oreln = smgropen(bufHdr->tag.rnode, MyBackendId);
+ oreln = smgropen(bufHdr->tag.rnode.node, MyBackendId);
PageSetChecksumInplace(localpage, bufHdr->tag.blockNum);
@@ -331,14 +331,14 @@ DropRelFileNodeLocalBuffers(RelFileNode rnode, ForkNumber forkNum,
buf_state = pg_atomic_read_u32(&bufHdr->state);
if ((buf_state & BM_TAG_VALID) &&
- RelFileNodeEquals(bufHdr->tag.rnode, rnode) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, rnode) &&
bufHdr->tag.forkNum == forkNum &&
bufHdr->tag.blockNum >= firstDelBlock)
{
if (LocalRefCount[i] != 0)
elog(ERROR, "block %u of %s is still referenced (local %u)",
bufHdr->tag.blockNum,
- relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum),
LocalRefCount[i]);
/* Remove entry from hashtable */
@@ -377,12 +377,12 @@ DropRelFileNodeAllLocalBuffers(RelFileNode rnode)
buf_state = pg_atomic_read_u32(&bufHdr->state);
if ((buf_state & BM_TAG_VALID) &&
- RelFileNodeEquals(bufHdr->tag.rnode, rnode))
+ RelFileNodeEquals(bufHdr->tag.rnode.node, rnode))
{
if (LocalRefCount[i] != 0)
elog(ERROR, "block %u of %s is still referenced (local %u)",
bufHdr->tag.blockNum,
- relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum),
LocalRefCount[i]);
/* Remove entry from hashtable */
diff --git a/src/backend/storage/freespace/fsmpage.c b/src/backend/storage/freespace/fsmpage.c
index cf7f03f..65eb422 100644
--- a/src/backend/storage/freespace/fsmpage.c
+++ b/src/backend/storage/freespace/fsmpage.c
@@ -268,13 +268,13 @@ restart:
*
* Fix the corruption and restart.
*/
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forknum;
BlockNumber blknum;
BufferGetTag(buf, &rnode, &forknum, &blknum);
elog(DEBUG1, "fixing corrupt FSM block %u, relation %u/%u/%u",
- blknum, rnode.spcNode, rnode.dbNode, rnode.relNode);
+ blknum, rnode.node.spcNode, rnode.node.dbNode, rnode.node.relNode);
/* make sure we hold an exclusive lock */
if (!exclusive_lock_held)
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 07f3c93..204c4cb 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,48 @@ 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)
+ {
+ /* Remove relation pages from shared buffers */
+ DropRelFileNodesAllBuffers(&rel->rnode, 1);
+
+ /* 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 +273,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 +522,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 +546,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 +723,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 +814,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 2488607..86e8fca 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 0cc9ede..1dff0c8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15593,8 +15593,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/buf_internals.h b/src/include/storage/buf_internals.h
index df2dda7..7adb96b 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -90,16 +90,17 @@
*/
typedef struct buftag
{
- RelFileNode rnode; /* physical relation identifier */
+ RelFileNodeBackend rnode; /* physical relation identifier */
ForkNumber forkNum;
BlockNumber blockNum; /* blknum relative to begin of reln */
} BufferTag;
#define CLEAR_BUFFERTAG(a) \
( \
- (a).rnode.spcNode = InvalidOid, \
- (a).rnode.dbNode = InvalidOid, \
- (a).rnode.relNode = InvalidOid, \
+ (a).rnode.node.spcNode = InvalidOid, \
+ (a).rnode.node.dbNode = InvalidOid, \
+ (a).rnode.node.relNode = InvalidOid, \
+ (a).rnode.backend = InvalidBackendId, \
(a).forkNum = InvalidForkNumber, \
(a).blockNum = InvalidBlockNumber \
)
@@ -113,7 +114,7 @@ typedef struct buftag
#define BUFFERTAGS_EQUAL(a,b) \
( \
- RelFileNodeEquals((a).rnode, (b).rnode) && \
+ RelFileNodeBackendEquals((a).rnode, (b).rnode) && \
(a).blockNum == (b).blockNum && \
(a).forkNum == (b).forkNum \
)
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 509f4b7..3315fa0 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -205,7 +205,7 @@ extern XLogRecPtr BufferGetLSNAtomic(Buffer buffer);
extern void PrintPinnedBufs(void);
#endif
extern Size BufferShmemSize(void);
-extern void BufferGetTag(Buffer buffer, RelFileNode *rnode,
+extern void BufferGetTag(Buffer buffer, RelFileNodeBackend *rnode,
ForkNumber *forknum, BlockNumber *blknum);
extern void MarkBufferDirtyHint(Buffer buffer, bool buffer_std);
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 b0fe19e..b361851 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -328,6 +328,17 @@ 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
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 74b5077..44df4e0 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -85,3 +85,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;
[text/x-patch] global_private_temp-2.patch (51.9K, ../../91de2094-165a-04f1-9b3d-348473394345@postgrespro.ru/4-global_private_temp-2.patch)
download | inline diff:
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 9726020..c99701d 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/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f1ff01e..e92d324 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 9c1f7de..97cc9e4 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/catalog/catalog.c b/src/backend/catalog/catalog.c
index a065419..8814afb 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -409,6 +409,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 3e1d406..aaa2c49 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3590,7 +3590,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 3cc886f..a111ddc 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 cedb4ee..d11c5b3 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 0960b33..6c3998f 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 fb2be10..a31f775 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -586,7 +586,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);
@@ -7678,6 +7679,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,
@@ -14082,6 +14089,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 */
@@ -14569,14 +14583,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 c97bb36..f9b2000 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3265,20 +3265,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 6e5768c..ea6989b 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 073f313..3383c35 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2069,7 +2069,7 @@ do_autovacuum(void)
* Check if it is a temp table (presumably, of some other backend's).
* We cannot safely process other backends' temp tables.
*/
- if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+ if (IsLocalRelpersistence(classForm->relpersistence))
{
/*
* We just ignore it if the owning backend is still active and
@@ -2154,7 +2154,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 2488607..86e8fca 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 0cc9ede..1dff0c8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15593,8 +15593,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 b0fe19e..dfa2044 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -328,6 +328,17 @@ 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
@@ -524,7 +535,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 69ae227..95919f8 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -87,3 +87,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;
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-16 06:25 Craig Ringer <craig@2ndquadrant.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Craig Ringer @ 2019-08-16 06:25 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
On Tue, 13 Aug 2019 at 21:50, Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
wrote:
> As far as I understand relpages and reltuples are set only when you
>> perform "analyze" of the table.
>>
>
> Also autovacuum's autoanalyze.
>
>
> When it happen?
> I have created normal table, populated it with some data and then wait
> several hours but pg_class was not updated for this table.
>
>
heap_vacuum_rel() in src/backend/access/heap/vacuumlazy.c below
* Update statistics in pg_class.
which I'm pretty sure is common to explicit vacuum and autovacuum. I
haven't run up a test to verify 100% but most DBs would never have relpages
etc set if autovac didn't do it since most aren't explicitly VACUUMed at
all.
I thought it was done when autovac ran an analyze, but it looks like it's
all autovac. Try setting very aggressive autovac thresholds and inserting +
deleting a bunch of tuples maybe.
I attach to this mail slightly refactored versions of this patches with
> fixes of issues reported in your review.
>
Thanks.
Did you have a chance to consider my questions too? I see a couple of
things where there's no patch change, which is fine, but I'd be interested
in your thoughts on the question/issue in those cases.
>
--
Craig Ringer http://www.2ndQuadrant.com/
2ndQuadrant - PostgreSQL Solutions for the Enterprise
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-16 07:30 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Craig Ringer <craig@2ndquadrant.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-08-16 07:30 UTC (permalink / raw)
To: Craig Ringer <craig@2ndquadrant.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
On 16.08.2019 9:25, Craig Ringer wrote:
> On Tue, 13 Aug 2019 at 21:50, Konstantin Knizhnik
> <k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> wrote:
>
>> As far as I understand relpages and reltuples are set only
>> when you perform "analyze" of the table.
>>
>>
>> Also autovacuum's autoanalyze.
>
> When it happen?
> I have created normal table, populated it with some data and then
> wait several hours but pg_class was not updated for this table.
>
>
>
> heap_vacuum_rel() in src/backend/access/heap/vacuumlazy.c below
>
> * Update statistics in pg_class.
>
> which I'm pretty sure is common to explicit vacuum and autovacuum. I
> haven't run up a test to verify 100% but most DBs would never have
> relpages etc set if autovac didn't do it since most aren't explicitly
> VACUUMed at all.
Sorry, I already understood it myself.
But to make vacuum process the table it is necessary to remove or update
some rows in it.
It seems to be yet another Postgres problem, which was noticed by
Darafei Praliaskouski some time ago: append-only tables are never
proceeded by autovacuum.
>
> I thought it was done when autovac ran an analyze, but it looks like
> it's all autovac. Try setting very aggressive autovac thresholds and
> inserting + deleting a bunch of tuples maybe.
>
> I attach to this mail slightly refactored versions of this patches
> with fixes of issues reported in your review.
>
>
> Thanks.
>
> Did you have a chance to consider my questions too? I see a couple of
> things where there's no patch change, which is fine, but I'd be
> interested in your thoughts on the question/issue in those cases.
>
>
Sorry, may be I didn't notice some your questions. I have a filling that
I have replied on all your comments/questions.
Right now I reread all this thread and see two open issues:
1. Statistic for global temporary tables (including number of tuples,
pages and all visible flag).
My position is the following: while in most cases it should not be a
problem, because users rarely create indexes or do analyze for temporary
tables,
there can be situations when differences in data sets of global
temporary tables in different backends can really be a problem.
Unfortunately I can not propose good solution for this problem. It is
certainly possible to create some private (per-backend) cache for this
metadata.
But it seems to requires changes in many places.
2. Your concerns about performance penalty of global temp tables
accessed through shared buffers comparing with local temp tables access
through local buffers.
I think that this concern is not actual any more because there is
implementation of global temp tables using local buffers.
But my experiments doesn't show significant difference in access speed
of shared and local buffers. As far as shared buffers are used to be
much larger than local buffers,
there are more chances to hold all temp relation in memory without
spilling it to the disk. In this case access to global temp table will
be much faster comparing with access to
local temp tables. But the fact is that right now in the most frequent
scenario of temp table usage:
SELECT ... FROM PersistentTable INTO TempTable WHERE ...;
SELECT * FROM TempTable;
local temp table are more efficient than global temp table access
through shared buffer.
I think it is explained by caching and eviction policies.
In case of pulling all content of temp table in memory (pg_prewarm)
global temp table with shared buffers becomes faster.
I forget or do not notice some of your questions, would you be so kind
as to repeat them?
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-16 08:32 Craig Ringer <craig@2ndquadrant.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 2 replies; 525+ messages in thread
From: Craig Ringer @ 2019-08-16 08:32 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
On Fri, 16 Aug 2019 at 15:30, Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
wrote:
>
> 1. Statistic for global temporary tables (including number of tuples,
> pages and all visible flag).
> My position is the following: while in most cases it should not be a
> problem, because users rarely create indexes or do analyze for temporary
> tables,
> there can be situations when differences in data sets of global temporary
> tables in different backends can really be a problem.
> Unfortunately I can not propose good solution for this problem. It is
> certainly possible to create some private (per-backend) cache for this
> metadata.
> But it seems to requires changes in many places.
>
Yeah. I don't really like just sharing them but it's not that bad either.
> 2. Your concerns about performance penalty of global temp tables accessed
> through shared buffers comparing with local temp tables access through
> local buffers.
> I think that this concern is not actual any more because there is
> implementation of global temp tables using local buffers.
> But my experiments doesn't show significant difference in access speed of
> shared and local buffers. As far as shared buffers are used to be much
> larger than local buffers,
> there are more chances to hold all temp relation in memory without
> spilling it to the disk. In this case access to global temp table will be
> much faster comparing with access to
> local temp tables.
>
You ignore the costs of evicting non-temporary data from shared_buffers,
i.e. contention for space. Also increased chance of backends being forced
to do direct write-out due to lack of s_b space for dirty buffers.
> In case of pulling all content of temp table in memory (pg_prewarm)
global temp table with shared buffers becomes faster.
Who would ever do that?
I forget or do not notice some of your questions, would you be so kind as
> to repeat them?
>
--
Craig Ringer http://www.2ndQuadrant.com/
2ndQuadrant - PostgreSQL Solutions for the Enterprise
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-16 08:37 Craig Ringer <craig@2ndquadrant.com>
parent: Craig Ringer <craig@2ndquadrant.com>
1 sibling, 1 reply; 525+ messages in thread
From: Craig Ringer @ 2019-08-16 08:37 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
>
>
> On Fri, 16 Aug 2019 at 15:30, Konstantin Knizhnik <
> k.knizhnik@postgrespro.ru> wrote:
>
>
>> I forget or do not notice some of your questions, would you be so kind as
>> to repeat them?
>>
>
>
Sent early by accident.
Repeating questions:
Why do you need to do all this indirection with changing RelFileNode to
RelFileNodeBackend in the bufmgr, changing BufferGetTag etc? Similarly,
your changes of RelFileNodeBackendIsTemp to RelFileNodeBackendIsLocalTemp .
I'm guessing you did it the way you did instead to lay the groundwork for
cross-backend sharing, but if so it should IMO be in your second patch that
adds support for using shared_buffers for temp tables, not in the first
patch that adds a minimal global temp tables implementation. Maybe my
understanding of the existing temp table mechanics is just insufficient as
I see RelFileNodeBackendIsTemp is already used in some aspects of existing
temp relation handling.
Did you look into my suggestion of extending the relmapper so that global
temp tables would have a relfilenode of 0 like pg_class etc, and use a
backend-local map of oid-to-relfilenode mappings?
Similarly, TruncateSessionRelations probably shouldn't need to exist in
this patch in its current form; there's no shared_buffers use to clean and
the same file cleanup mechanism should handle both session-temp and
local-temp relfilenodes.
Sequence initialization ignores sequence startval/firstval settings. Why?
+ value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(1); /* start
sequence with 1 */
Doesn't this change the test outcome for RELPERSISTENCE_UNLOGGED?:
- else if (newrelpersistence == RELPERSISTENCE_PERMANENT)
+ else if (newrelpersistence != RELPERSISTENCE_TEMP)
--
Craig Ringer http://www.2ndQuadrant.com/
2ndQuadrant - PostgreSQL Solutions for the Enterprise
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-16 11:21 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Craig Ringer <craig@2ndquadrant.com>
0 siblings, 0 replies; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-08-16 11:21 UTC (permalink / raw)
To: Craig Ringer <craig@2ndquadrant.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
On 16.08.2019 11:37, Craig Ringer wrote:
>
>
> On Fri, 16 Aug 2019 at 15:30, Konstantin Knizhnik
> <k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> wrote:
>
> I forget or do not notice some of your questions, would you be
> so kind as to repeat them?
>
>
> Sent early by accident.
>
> Repeating questions:
Sorry, but I have answered them (my e-mail from 13.08)!
Looks like you have looed at wrong version of the patch:
global_shared_temp-1.patch instead of global_private_temp-1.patch which
implements global tables accessed through local buffers.
>
>
> Why do you need to do all this indirection with changing RelFileNode
> to RelFileNodeBackend in the bufmgr, changing BufferGetTag etc?
> Similarly, your changes of RelFileNodeBackendIsTemp
> to RelFileNodeBackendIsLocalTemp . I'm guessing you did it the way you
> did instead to lay the groundwork for cross-backend sharing, but if so
> it should IMO be in your second patch that adds support for using
> shared_buffers for temp tables, not in the first patch that adds a
> minimal global temp tables implementation. Maybe my understanding of
> the existing temp table mechanics is just insufficient as I
> see RelFileNodeBackendIsTemp is already used in some aspects of
> existing temp relation handling.
>
Sorry, are you really speaking about global_private_temp-1.patch?
This patch doesn't change bufmgr file at all.
May be you looked at another patch - global_shared_temp-1.patch
which is accessing shared tables though shared buffers and so have to
change buffer tag to include backend ID in it.
> Did you look into my suggestion of extending the relmapper so that
> global temp tables would have a relfilenode of 0 like pg_class etc,
> and use a backend-local map of oid-to-relfilenode mappings?
>
> Similarly, TruncateSessionRelations probably shouldn't need to exist
> in this patch in its current form; there's no shared_buffers use to
> clean and the same file cleanup mechanism should handle both
> session-temp and local-temp relfilenodes.
In global_private_temp-1.patch TruncateSessionRelations does nothing
with shared buffers, it just delete relation files.
>
> Sequence initialization ignores sequence startval/firstval settings. Why?
> + value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(1); /*
> start sequence with 1 */
>
>
>
I am handling only case of implicitly created sequences for
SERIAL/BIGSERIAL columns.
Is it possible to explicitly specify initial value and step for them?
If so, this place should definitely be rewritten.
> Doesn't this change the test outcome for RELPERSISTENCE_UNLOGGED?:
> - else if (newrelpersistence == RELPERSISTENCE_PERMANENT)
> + else if (newrelpersistence != RELPERSISTENCE_TEMP)
>
RELPERSISTENCE_UNLOGGED case is handle in previous IF branch.
-
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-16 11:41 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Craig Ringer <craig@2ndquadrant.com>
1 sibling, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-08-16 11:41 UTC (permalink / raw)
To: Craig Ringer <craig@2ndquadrant.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
On 16.08.2019 11:32, Craig Ringer wrote:
>
> You ignore the costs of evicting non-temporary data from
> shared_buffers, i.e. contention for space. Also increased chance of
> backends being forced to do direct write-out due to lack of s_b space
> for dirty buffers.
> > In case of pulling all content of temp table in memory (pg_prewarm)
> global temp table with shared buffers becomes faster.
>
> Who would ever do that?
>
>
I decided to redo my experiments and now get different results which
illustrates advantages of global temp tables with shared buffer.
I performed the following test at my desktop with SSD and 16GB of RAM
and Postgres with default configuration except shared-buffers increased
to 1Gb.
postgres=# create table big(pk bigint primary key, val bigint);
CREATE TABLE
postgres=# insert into big values
(generate_series(1,100000000),generate_series(1,100000000)/100);
INSERT 0 100000000
postgres=# select * from buffer_usage limit 3;
relname | buffered | buffer_percent | percent_of_relation
----------------+------------+----------------+---------------------
big | 678 MB | 66.2 | 16.1
big_pkey | 344 MB | 33.6 | 16.1
pg_am | 8192 bytes | 0.0 | 20.0
postgres=# create temp table lt(key bigint, count bigint);
postgres=# \timing
Timing is on.
postgres=# insert into lt (select count(*),val as key from big group by
val);
INSERT 0 1000001
Time: 43265.491 ms (00:43.265)
postgres=# select sum(count) from lt;
sum
--------------
500000500000
(1 row)
Time: 94.194 ms
postgres=# insert into gt (select count(*),val as key from big group by
val);
INSERT 0 1000001
Time: 42952.671 ms (00:42.953)
postgres=# select sum(count) from gt;
sum
--------------
500000500000
(1 row)
Time: 35.906 ms
postgres=# select * from buffer_usage limit 3;
relname | buffered | buffer_percent | percent_of_relation
----------+----------+----------------+---------------------
big | 679 MB | 66.3 | 16.1
big_pkey | 300 MB | 29.3 | 14.0
gt | 42 MB | 4.1 | 100.0
So time of storing result in global temp table is slightly smaller than
time of storing it in local temp table and time of scanning global temp
table is twice smaller!
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-16 14:12 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-08-16 14:12 UTC (permalink / raw)
To: Craig Ringer <craig@2ndquadrant.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
I did more investigations of performance of global temp tables with
shared buffers vs. vanilla (local) temp tables.
1. Combination of persistent and temporary tables in the same query.
Preparation:
create table big(pk bigint primary key, val bigint);
insert into big values
(generate_series(1,100000000),generate_series(1,100000000));
create temp table lt(key bigint, count bigint);
create global temp table gt(key bigint, count bigint);
Size of table is about 6Gb, I run this test on desktop with 16GB of RAM
and postgres with 1Gb shared buffers.
I run two queries:
insert into T (select count(*),pk/P as key from big group by key);
select sum(count) from T;
where P is (100,10,1) and T is name of temp table (lt or gt).
The table below contains times of both queries in msec:
Percent of selected data
1%
10%
100%
Local temp table
44610
90
47920
891
63414
21612
Global temp table
44669
35
47939
298
59159
26015
As you can see, time of insertion in temporary table is almost the same
and time of traversal of temporary table is about twice smaller for
global temp table
when it fits in RAM together with persistent table and slightly worser
when it doesn't fit.
2. Temporary table only access.
The same system, but Postgres is configured with shared_buffers=10GB,
max_parallel_workers = 4, max_parallel_workers_per_gather = 4
Local temp tables:
create temp table local_temp(x1 bigint, x2 bigint, x3 bigint, x4 bigint,
x5 bigint, x6 bigint, x7 bigint, x8 bigint, x9 bigint);
insert into local_temp values
(generate_series(1,100000000),0,0,0,0,0,0,0,0);
select sum(x1) from local_temp;
Global temp tables:
create global temporary table global_temp(x1 bigint, x2 bigint, x3
bigint, x4 bigint, x5 bigint, x6 bigint, x7 bigint, x8 bigint, x9 bigint);
insert into global_temp values
(generate_series(1,100000000),0,0,0,0,0,0,0,0);
select sum(x1) from global_temp;
Results (msec):
Insert
Select
Local temp table 37489
48322
Global temp table 44358
3003
So insertion in local temp table is performed slightly faster but select
is 16 times slower!
Conclusion:
In the assumption then temp table fits in memory, global temp tables
with shared buffers provides better performance than local temp table.
I didn't consider here global temp tables with local buffers because for
them results should be similar with local temp tables.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-16 17:17 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2019-08-16 17:17 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
pá 16. 8. 2019 v 16:12 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:
> I did more investigations of performance of global temp tables with shared
> buffers vs. vanilla (local) temp tables.
>
> 1. Combination of persistent and temporary tables in the same query.
>
> Preparation:
> create table big(pk bigint primary key, val bigint);
> insert into big values
> (generate_series(1,100000000),generate_series(1,100000000));
> create temp table lt(key bigint, count bigint);
> create global temp table gt(key bigint, count bigint);
>
> Size of table is about 6Gb, I run this test on desktop with 16GB of RAM
> and postgres with 1Gb shared buffers.
> I run two queries:
>
> insert into T (select count(*),pk/P as key from big group by key);
> select sum(count) from T;
>
> where P is (100,10,1) and T is name of temp table (lt or gt).
> The table below contains times of both queries in msec:
>
> Percent of selected data
> 1%
> 10%
> 100%
> Local temp table
> 44610
> 90
> 47920
> 891
> 63414
> 21612
> Global temp table
> 44669
> 35
> 47939
> 298
> 59159
> 26015
>
> As you can see, time of insertion in temporary table is almost the same
> and time of traversal of temporary table is about twice smaller for global
> temp table
> when it fits in RAM together with persistent table and slightly worser
> when it doesn't fit.
>
>
>
> 2. Temporary table only access.
> The same system, but Postgres is configured with shared_buffers=10GB,
> max_parallel_workers = 4, max_parallel_workers_per_gather = 4
>
> Local temp tables:
> create temp table local_temp(x1 bigint, x2 bigint, x3 bigint, x4 bigint,
> x5 bigint, x6 bigint, x7 bigint, x8 bigint, x9 bigint);
> insert into local_temp values
> (generate_series(1,100000000),0,0,0,0,0,0,0,0);
> select sum(x1) from local_temp;
>
> Global temp tables:
> create global temporary table global_temp(x1 bigint, x2 bigint, x3 bigint,
> x4 bigint, x5 bigint, x6 bigint, x7 bigint, x8 bigint, x9 bigint);
> insert into global_temp values
> (generate_series(1,100000000),0,0,0,0,0,0,0,0);
> select sum(x1) from global_temp;
>
> Results (msec):
>
> Insert
> Select
> Local temp table 37489
> 48322
> Global temp table 44358
> 3003
>
> So insertion in local temp table is performed slightly faster but select
> is 16 times slower!
>
> Conclusion:
> In the assumption then temp table fits in memory, global temp tables with
> shared buffers provides better performance than local temp table.
> I didn't consider here global temp tables with local buffers because for
> them results should be similar with local temp tables.
>
Probably there is not a reason why shared buffers should be slower than
local buffers when system is under low load.
access to shared memory is protected by spin locks (are cheap for few
processes), so tests in one or few process are not too important (or it is
just one side of space)
another topic can be performance on MS Sys - there are stories about not
perfect performance of shared memory there.
Regards
Pavel
>
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-18 07:01 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-08-18 07:01 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
On 16.08.2019 20:17, Pavel Stehule wrote:
>
>
> pá 16. 8. 2019 v 16:12 odesílatel Konstantin Knizhnik
> <k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> napsal:
>
> I did more investigations of performance of global temp tables
> with shared buffers vs. vanilla (local) temp tables.
>
> 1. Combination of persistent and temporary tables in the same query.
>
> Preparation:
> create table big(pk bigint primary key, val bigint);
> insert into big values
> (generate_series(1,100000000),generate_series(1,100000000));
> create temp table lt(key bigint, count bigint);
> create global temp table gt(key bigint, count bigint);
>
> Size of table is about 6Gb, I run this test on desktop with 16GB
> of RAM and postgres with 1Gb shared buffers.
> I run two queries:
>
> insert into T (select count(*),pk/P as key from big group by key);
> select sum(count) from T;
>
> where P is (100,10,1) and T is name of temp table (lt or gt).
> The table below contains times of both queries in msec:
>
> Percent of selected data
> 1%
> 10%
> 100%
> Local temp table
> 44610
> 90
> 47920
> 891
> 63414
> 21612
> Global temp table
> 44669
> 35
> 47939
> 298
> 59159
> 26015
>
>
> As you can see, time of insertion in temporary table is almost the
> same
> and time of traversal of temporary table is about twice smaller
> for global temp table
> when it fits in RAM together with persistent table and slightly
> worser when it doesn't fit.
>
>
>
> 2. Temporary table only access.
> The same system, but Postgres is configured with
> shared_buffers=10GB, max_parallel_workers = 4,
> max_parallel_workers_per_gather = 4
>
> Local temp tables:
> create temp table local_temp(x1 bigint, x2 bigint, x3 bigint, x4
> bigint, x5 bigint, x6 bigint, x7 bigint, x8 bigint, x9 bigint);
> insert into local_temp values
> (generate_series(1,100000000),0,0,0,0,0,0,0,0);
> select sum(x1) from local_temp;
>
> Global temp tables:
> create global temporary table global_temp(x1 bigint, x2 bigint, x3
> bigint, x4 bigint, x5 bigint, x6 bigint, x7 bigint, x8 bigint, x9
> bigint);
> insert into global_temp values
> (generate_series(1,100000000),0,0,0,0,0,0,0,0);
> select sum(x1) from global_temp;
>
> Results (msec):
>
> Insert
> Select
> Local temp table 37489
> 48322
> Global temp table 44358
> 3003
>
>
> So insertion in local temp table is performed slightly faster but
> select is 16 times slower!
>
> Conclusion:
> In the assumption then temp table fits in memory, global temp
> tables with shared buffers provides better performance than local
> temp table.
> I didn't consider here global temp tables with local buffers
> because for them results should be similar with local temp tables.
>
>
> Probably there is not a reason why shared buffers should be slower
> than local buffers when system is under low load.
>
> access to shared memory is protected by spin locks (are cheap for few
> processes), so tests in one or few process are not too important (or
> it is just one side of space)
>
> another topic can be performance on MS Sys - there are stories about
> not perfect performance of shared memory there.
>
> Regards
>
> Pavel
>
One more test which is used to simulate access to temp tables under
high load.
I am using "upsert" into temp table in multiple connections.
create global temp table gtemp (x integer primary key, y bigint);
upsert.sql:
insert into gtemp values (random() * 1000000, 0) on conflict(x) do
update set y=gtemp.y+1;
pgbench -c 10 -M prepared -T 100 -P 1 -n -f upsert.sql postgres
I failed to find some standard way in pgbech to perform per-session
initialization to create local temp table,
so I just insert this code in pgbench code:
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index 570cf33..af6a431 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -5994,6 +5994,7 @@ threadRun(void *arg)
{
if ((state[i].con = doConnect()) == NULL)
goto done;
+ executeStatement(state[i].con, "create temp
table ltemp(x integer primary key, y bigint)");
}
}
Results are the following:
Global temp table: 117526 TPS
Local temp table: 107802 TPS
So even for this workload global temp table with shared buffers are a
little bit faster.
I will be pleased if you can propose some other testing scenario.
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-18 08:28 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2019-08-18 08:28 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
ne 18. 8. 2019 v 9:02 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:
>
>
> On 16.08.2019 20:17, Pavel Stehule wrote:
>
>
>
> pá 16. 8. 2019 v 16:12 odesílatel Konstantin Knizhnik <
> k.knizhnik@postgrespro.ru> napsal:
>
>> I did more investigations of performance of global temp tables with
>> shared buffers vs. vanilla (local) temp tables.
>>
>> 1. Combination of persistent and temporary tables in the same query.
>>
>> Preparation:
>> create table big(pk bigint primary key, val bigint);
>> insert into big values
>> (generate_series(1,100000000),generate_series(1,100000000));
>> create temp table lt(key bigint, count bigint);
>> create global temp table gt(key bigint, count bigint);
>>
>> Size of table is about 6Gb, I run this test on desktop with 16GB of RAM
>> and postgres with 1Gb shared buffers.
>> I run two queries:
>>
>> insert into T (select count(*),pk/P as key from big group by key);
>> select sum(count) from T;
>>
>> where P is (100,10,1) and T is name of temp table (lt or gt).
>> The table below contains times of both queries in msec:
>>
>> Percent of selected data
>> 1%
>> 10%
>> 100%
>> Local temp table
>> 44610
>> 90
>> 47920
>> 891
>> 63414
>> 21612
>> Global temp table
>> 44669
>> 35
>> 47939
>> 298
>> 59159
>> 26015
>>
>> As you can see, time of insertion in temporary table is almost the same
>> and time of traversal of temporary table is about twice smaller for
>> global temp table
>> when it fits in RAM together with persistent table and slightly worser
>> when it doesn't fit.
>>
>>
>>
>> 2. Temporary table only access.
>> The same system, but Postgres is configured with shared_buffers=10GB,
>> max_parallel_workers = 4, max_parallel_workers_per_gather = 4
>>
>> Local temp tables:
>> create temp table local_temp(x1 bigint, x2 bigint, x3 bigint, x4 bigint,
>> x5 bigint, x6 bigint, x7 bigint, x8 bigint, x9 bigint);
>> insert into local_temp values
>> (generate_series(1,100000000),0,0,0,0,0,0,0,0);
>> select sum(x1) from local_temp;
>>
>> Global temp tables:
>> create global temporary table global_temp(x1 bigint, x2 bigint, x3
>> bigint, x4 bigint, x5 bigint, x6 bigint, x7 bigint, x8 bigint, x9 bigint);
>> insert into global_temp values
>> (generate_series(1,100000000),0,0,0,0,0,0,0,0);
>> select sum(x1) from global_temp;
>>
>> Results (msec):
>>
>> Insert
>> Select
>> Local temp table 37489
>> 48322
>> Global temp table 44358
>> 3003
>>
>> So insertion in local temp table is performed slightly faster but select
>> is 16 times slower!
>>
>> Conclusion:
>> In the assumption then temp table fits in memory, global temp tables with
>> shared buffers provides better performance than local temp table.
>> I didn't consider here global temp tables with local buffers because for
>> them results should be similar with local temp tables.
>>
>
> Probably there is not a reason why shared buffers should be slower than
> local buffers when system is under low load.
>
> access to shared memory is protected by spin locks (are cheap for few
> processes), so tests in one or few process are not too important (or it is
> just one side of space)
>
> another topic can be performance on MS Sys - there are stories about not
> perfect performance of shared memory there.
>
> Regards
>
> Pavel
>
> One more test which is used to simulate access to temp tables under high
> load.
> I am using "upsert" into temp table in multiple connections.
>
> create global temp table gtemp (x integer primary key, y bigint);
>
> upsert.sql:
> insert into gtemp values (random() * 1000000, 0) on conflict(x) do update
> set y=gtemp.y+1;
>
> pgbench -c 10 -M prepared -T 100 -P 1 -n -f upsert.sql postgres
>
>
> I failed to find some standard way in pgbech to perform per-session
> initialization to create local temp table,
> so I just insert this code in pgbench code:
>
> diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
> index 570cf33..af6a431 100644
> --- a/src/bin/pgbench/pgbench.c
> +++ b/src/bin/pgbench/pgbench.c
> @@ -5994,6 +5994,7 @@ threadRun(void *arg)
> {
> if ((state[i].con = doConnect()) == NULL)
> goto done;
> + executeStatement(state[i].con, "create temp table
> ltemp(x integer primary key, y bigint)");
> }
> }
>
>
> Results are the following:
> Global temp table: 117526 TPS
> Local temp table: 107802 TPS
>
>
> So even for this workload global temp table with shared buffers are a
> little bit faster.
> I will be pleased if you can propose some other testing scenario.
>
please, try to increase number of connections.
Regards
Pavel
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-19 08:51 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-08-19 08:51 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
On 18.08.2019 11:28, Pavel Stehule wrote:
>
>
> ne 18. 8. 2019 v 9:02 odesílatel Konstantin Knizhnik
> <k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> napsal:
>
>
>
> On 16.08.2019 20:17, Pavel Stehule wrote:
>>
>>
>> pá 16. 8. 2019 v 16:12 odesílatel Konstantin Knizhnik
>> <k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>>
>> napsal:
>>
>> I did more investigations of performance of global temp
>> tables with shared buffers vs. vanilla (local) temp tables.
>>
>> 1. Combination of persistent and temporary tables in the same
>> query.
>>
>> Preparation:
>> create table big(pk bigint primary key, val bigint);
>> insert into big values
>> (generate_series(1,100000000),generate_series(1,100000000));
>> create temp table lt(key bigint, count bigint);
>> create global temp table gt(key bigint, count bigint);
>>
>> Size of table is about 6Gb, I run this test on desktop with
>> 16GB of RAM and postgres with 1Gb shared buffers.
>> I run two queries:
>>
>> insert into T (select count(*),pk/P as key from big group by
>> key);
>> select sum(count) from T;
>>
>> where P is (100,10,1) and T is name of temp table (lt or gt).
>> The table below contains times of both queries in msec:
>>
>> Percent of selected data
>> 1%
>> 10%
>> 100%
>> Local temp table
>> 44610
>> 90
>> 47920
>> 891
>> 63414
>> 21612
>> Global temp table
>> 44669
>> 35
>> 47939
>> 298
>> 59159
>> 26015
>>
>>
>> As you can see, time of insertion in temporary table is
>> almost the same
>> and time of traversal of temporary table is about twice
>> smaller for global temp table
>> when it fits in RAM together with persistent table and
>> slightly worser when it doesn't fit.
>>
>>
>>
>> 2. Temporary table only access.
>> The same system, but Postgres is configured with
>> shared_buffers=10GB, max_parallel_workers = 4,
>> max_parallel_workers_per_gather = 4
>>
>> Local temp tables:
>> create temp table local_temp(x1 bigint, x2 bigint, x3 bigint,
>> x4 bigint, x5 bigint, x6 bigint, x7 bigint, x8 bigint, x9
>> bigint);
>> insert into local_temp values
>> (generate_series(1,100000000),0,0,0,0,0,0,0,0);
>> select sum(x1) from local_temp;
>>
>> Global temp tables:
>> create global temporary table global_temp(x1 bigint, x2
>> bigint, x3 bigint, x4 bigint, x5 bigint, x6 bigint, x7
>> bigint, x8 bigint, x9 bigint);
>> insert into global_temp values
>> (generate_series(1,100000000),0,0,0,0,0,0,0,0);
>> select sum(x1) from global_temp;
>>
>> Results (msec):
>>
>> Insert
>> Select
>> Local temp table 37489
>> 48322
>> Global temp table 44358
>> 3003
>>
>>
>> So insertion in local temp table is performed slightly faster
>> but select is 16 times slower!
>>
>> Conclusion:
>> In the assumption then temp table fits in memory, global temp
>> tables with shared buffers provides better performance than
>> local temp table.
>> I didn't consider here global temp tables with local buffers
>> because for them results should be similar with local temp
>> tables.
>>
>>
>> Probably there is not a reason why shared buffers should be
>> slower than local buffers when system is under low load.
>>
>> access to shared memory is protected by spin locks (are cheap for
>> few processes), so tests in one or few process are not too
>> important (or it is just one side of space)
>>
>> another topic can be performance on MS Sys - there are stories
>> about not perfect performance of shared memory there.
>>
>> Regards
>>
>> Pavel
>>
> One more test which is used to simulate access to temp tables
> under high load.
> I am using "upsert" into temp table in multiple connections.
>
> create global temp table gtemp (x integer primary key, y bigint);
>
> upsert.sql:
> insert into gtemp values (random() * 1000000, 0) on conflict(x) do
> update set y=gtemp.y+1;
>
> pgbench -c 10 -M prepared -T 100 -P 1 -n -f upsert.sql postgres
>
>
> I failed to find some standard way in pgbech to perform
> per-session initialization to create local temp table,
> so I just insert this code in pgbench code:
>
> diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
> index 570cf33..af6a431 100644
> --- a/src/bin/pgbench/pgbench.c
> +++ b/src/bin/pgbench/pgbench.c
> @@ -5994,6 +5994,7 @@ threadRun(void *arg)
> {
> if ((state[i].con = doConnect()) == NULL)
> goto done;
> + executeStatement(state[i].con, "create
> temp table ltemp(x integer primary key, y bigint)");
> }
> }
>
>
> Results are the following:
> Global temp table: 117526 TPS
> Local temp table: 107802 TPS
>
>
> So even for this workload global temp table with shared buffers
> are a little bit faster.
> I will be pleased if you can propose some other testing scenario.
>
>
> please, try to increase number of connections.
With 20 connections and 4 pgbench threads results are similar: 119k TPS
for global temp tables and 115k TPS for local temp tables.
I have tried yet another scenario: read-only access to temp tables:
\set id random(1,10000000)
select sum(y) from ltemp where x=:id;
Tables are created and initialized in pgbench session startup:
knizhnik@knizhnik:~/postgresql$ git diff
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index 570cf33..95295b0 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -5994,6 +5994,8 @@ threadRun(void *arg)
{
if ((state[i].con = doConnect()) == NULL)
goto done;
+ executeStatement(state[i].con, "create temp
table ltemp(x integer primary key, y bigint)");
+ executeStatement(state[i].con, "insert into
ltemp values (generate_series(1,1000000), generate_series(1,1000000))");
}
}
Results for 10 connections with 10 million inserted records per table
and 100 connections with 1 million inserted record per table :
#connections:
10
100
local temp
68k
90k
global temp, shared_buffers=1G
63k
61k
global temp, shared_buffers=10G 150k
150k
So temporary tables with local buffers are slightly faster when data
doesn't fit in shared buffers, but significantly slower when it fits.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-19 11:16 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-08-19 11:16 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
On 19.08.2019 11:51, Konstantin Knizhnik wrote:
>
>
> On 18.08.2019 11:28, Pavel Stehule wrote:
>>
>>
>> ne 18. 8. 2019 v 9:02 odesílatel Konstantin Knizhnik
>> <k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> napsal:
>>
>>
>>
>> On 16.08.2019 20:17, Pavel Stehule wrote:
>>>
>>>
>>> pá 16. 8. 2019 v 16:12 odesílatel Konstantin Knizhnik
>>> <k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>>
>>> napsal:
>>>
>>> I did more investigations of performance of global temp
>>> tables with shared buffers vs. vanilla (local) temp tables.
>>>
>>> 1. Combination of persistent and temporary tables in the
>>> same query.
>>>
>>> Preparation:
>>> create table big(pk bigint primary key, val bigint);
>>> insert into big values
>>> (generate_series(1,100000000),generate_series(1,100000000));
>>> create temp table lt(key bigint, count bigint);
>>> create global temp table gt(key bigint, count bigint);
>>>
>>> Size of table is about 6Gb, I run this test on desktop with
>>> 16GB of RAM and postgres with 1Gb shared buffers.
>>> I run two queries:
>>>
>>> insert into T (select count(*),pk/P as key from big group by
>>> key);
>>> select sum(count) from T;
>>>
>>> where P is (100,10,1) and T is name of temp table (lt or gt).
>>> The table below contains times of both queries in msec:
>>>
>>> Percent of selected data
>>> 1%
>>> 10%
>>> 100%
>>> Local temp table
>>> 44610
>>> 90
>>> 47920
>>> 891
>>> 63414
>>> 21612
>>> Global temp table
>>> 44669
>>> 35
>>> 47939
>>> 298
>>> 59159
>>> 26015
>>>
>>>
>>> As you can see, time of insertion in temporary table is
>>> almost the same
>>> and time of traversal of temporary table is about twice
>>> smaller for global temp table
>>> when it fits in RAM together with persistent table and
>>> slightly worser when it doesn't fit.
>>>
>>>
>>>
>>> 2. Temporary table only access.
>>> The same system, but Postgres is configured with
>>> shared_buffers=10GB, max_parallel_workers = 4,
>>> max_parallel_workers_per_gather = 4
>>>
>>> Local temp tables:
>>> create temp table local_temp(x1 bigint, x2 bigint, x3
>>> bigint, x4 bigint, x5 bigint, x6 bigint, x7 bigint, x8
>>> bigint, x9 bigint);
>>> insert into local_temp values
>>> (generate_series(1,100000000),0,0,0,0,0,0,0,0);
>>> select sum(x1) from local_temp;
>>>
>>> Global temp tables:
>>> create global temporary table global_temp(x1 bigint, x2
>>> bigint, x3 bigint, x4 bigint, x5 bigint, x6 bigint, x7
>>> bigint, x8 bigint, x9 bigint);
>>> insert into global_temp values
>>> (generate_series(1,100000000),0,0,0,0,0,0,0,0);
>>> select sum(x1) from global_temp;
>>>
>>> Results (msec):
>>>
>>> Insert
>>> Select
>>> Local temp table 37489
>>> 48322
>>> Global temp table 44358
>>> 3003
>>>
>>>
>>> So insertion in local temp table is performed slightly
>>> faster but select is 16 times slower!
>>>
>>> Conclusion:
>>> In the assumption then temp table fits in memory, global
>>> temp tables with shared buffers provides better performance
>>> than local temp table.
>>> I didn't consider here global temp tables with local buffers
>>> because for them results should be similar with local temp
>>> tables.
>>>
>>>
>>> Probably there is not a reason why shared buffers should be
>>> slower than local buffers when system is under low load.
>>>
>>> access to shared memory is protected by spin locks (are cheap
>>> for few processes), so tests in one or few process are not too
>>> important (or it is just one side of space)
>>>
>>> another topic can be performance on MS Sys - there are stories
>>> about not perfect performance of shared memory there.
>>>
>>> Regards
>>>
>>> Pavel
>>>
>> One more test which is used to simulate access to temp tables
>> under high load.
>> I am using "upsert" into temp table in multiple connections.
>>
>> create global temp table gtemp (x integer primary key, y bigint);
>>
>> upsert.sql:
>> insert into gtemp values (random() * 1000000, 0) on conflict(x)
>> do update set y=gtemp.y+1;
>>
>> pgbench -c 10 -M prepared -T 100 -P 1 -n -f upsert.sql postgres
>>
>>
>> I failed to find some standard way in pgbech to perform
>> per-session initialization to create local temp table,
>> so I just insert this code in pgbench code:
>>
>> diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
>> index 570cf33..af6a431 100644
>> --- a/src/bin/pgbench/pgbench.c
>> +++ b/src/bin/pgbench/pgbench.c
>> @@ -5994,6 +5994,7 @@ threadRun(void *arg)
>> {
>> if ((state[i].con = doConnect()) == NULL)
>> goto done;
>> + executeStatement(state[i].con, "create
>> temp table ltemp(x integer primary key, y bigint)");
>> }
>> }
>>
>>
>> Results are the following:
>> Global temp table: 117526 TPS
>> Local temp table: 107802 TPS
>>
>>
>> So even for this workload global temp table with shared buffers
>> are a little bit faster.
>> I will be pleased if you can propose some other testing scenario.
>>
>>
>> please, try to increase number of connections.
>
> With 20 connections and 4 pgbench threads results are similar: 119k
> TPS for global temp tables and 115k TPS for local temp tables.
>
> I have tried yet another scenario: read-only access to temp tables:
>
> \set id random(1,10000000)
> select sum(y) from ltemp where x=:id;
>
> Tables are created and initialized in pgbench session startup:
>
> knizhnik@knizhnik:~/postgresql$ git diff
> diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
> index 570cf33..95295b0 100644
> --- a/src/bin/pgbench/pgbench.c
> +++ b/src/bin/pgbench/pgbench.c
> @@ -5994,6 +5994,8 @@ threadRun(void *arg)
> {
> if ((state[i].con = doConnect()) == NULL)
> goto done;
> + executeStatement(state[i].con, "create temp
> table ltemp(x integer primary key, y bigint)");
> + executeStatement(state[i].con, "insert into
> ltemp values (generate_series(1,1000000), generate_series(1,1000000))");
> }
> }
>
>
> Results for 10 connections with 10 million inserted records per table
> and 100 connections with 1 million inserted record per table :
>
> #connections:
> 10
> 100
> local temp
> 68k
> 90k
> global temp, shared_buffers=1G
> 63k
> 61k
> global temp, shared_buffers=10G 150k
> 150k
>
>
>
> So temporary tables with local buffers are slightly faster when data
> doesn't fit in shared buffers, but significantly slower when it fits.
>
>
All previously reported results were produced at my desktop.
I also run this read-only test on huge IBM server (POWER9, 2 NUMA nodes,
176 CPU, 1Tb RAM).
Here the difference between local and global tables is not so large:
Local temp: 739k TPS
Global temp: 924k TPS
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-19 11:25 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2019-08-19 11:25 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
po 19. 8. 2019 v 13:16 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:
>
>
> On 19.08.2019 11:51, Konstantin Knizhnik wrote:
>
>
>
> On 18.08.2019 11:28, Pavel Stehule wrote:
>
>
>
> ne 18. 8. 2019 v 9:02 odesílatel Konstantin Knizhnik <
> k.knizhnik@postgrespro.ru> napsal:
>
>>
>>
>> On 16.08.2019 20:17, Pavel Stehule wrote:
>>
>>
>>
>> pá 16. 8. 2019 v 16:12 odesílatel Konstantin Knizhnik <
>> k.knizhnik@postgrespro.ru> napsal:
>>
>>> I did more investigations of performance of global temp tables with
>>> shared buffers vs. vanilla (local) temp tables.
>>>
>>> 1. Combination of persistent and temporary tables in the same query.
>>>
>>> Preparation:
>>> create table big(pk bigint primary key, val bigint);
>>> insert into big values
>>> (generate_series(1,100000000),generate_series(1,100000000));
>>> create temp table lt(key bigint, count bigint);
>>> create global temp table gt(key bigint, count bigint);
>>>
>>> Size of table is about 6Gb, I run this test on desktop with 16GB of RAM
>>> and postgres with 1Gb shared buffers.
>>> I run two queries:
>>>
>>> insert into T (select count(*),pk/P as key from big group by key);
>>> select sum(count) from T;
>>>
>>> where P is (100,10,1) and T is name of temp table (lt or gt).
>>> The table below contains times of both queries in msec:
>>>
>>> Percent of selected data
>>> 1%
>>> 10%
>>> 100%
>>> Local temp table
>>> 44610
>>> 90
>>> 47920
>>> 891
>>> 63414
>>> 21612
>>> Global temp table
>>> 44669
>>> 35
>>> 47939
>>> 298
>>> 59159
>>> 26015
>>>
>>> As you can see, time of insertion in temporary table is almost the same
>>> and time of traversal of temporary table is about twice smaller for
>>> global temp table
>>> when it fits in RAM together with persistent table and slightly worser
>>> when it doesn't fit.
>>>
>>>
>>>
>>> 2. Temporary table only access.
>>> The same system, but Postgres is configured with shared_buffers=10GB,
>>> max_parallel_workers = 4, max_parallel_workers_per_gather = 4
>>>
>>> Local temp tables:
>>> create temp table local_temp(x1 bigint, x2 bigint, x3 bigint, x4 bigint,
>>> x5 bigint, x6 bigint, x7 bigint, x8 bigint, x9 bigint);
>>> insert into local_temp values
>>> (generate_series(1,100000000),0,0,0,0,0,0,0,0);
>>> select sum(x1) from local_temp;
>>>
>>> Global temp tables:
>>> create global temporary table global_temp(x1 bigint, x2 bigint, x3
>>> bigint, x4 bigint, x5 bigint, x6 bigint, x7 bigint, x8 bigint, x9 bigint);
>>> insert into global_temp values
>>> (generate_series(1,100000000),0,0,0,0,0,0,0,0);
>>> select sum(x1) from global_temp;
>>>
>>> Results (msec):
>>>
>>> Insert
>>> Select
>>> Local temp table 37489
>>> 48322
>>> Global temp table 44358
>>> 3003
>>>
>>> So insertion in local temp table is performed slightly faster but select
>>> is 16 times slower!
>>>
>>> Conclusion:
>>> In the assumption then temp table fits in memory, global temp tables
>>> with shared buffers provides better performance than local temp table.
>>> I didn't consider here global temp tables with local buffers because for
>>> them results should be similar with local temp tables.
>>>
>>
>> Probably there is not a reason why shared buffers should be slower than
>> local buffers when system is under low load.
>>
>> access to shared memory is protected by spin locks (are cheap for few
>> processes), so tests in one or few process are not too important (or it is
>> just one side of space)
>>
>> another topic can be performance on MS Sys - there are stories about not
>> perfect performance of shared memory there.
>>
>> Regards
>>
>> Pavel
>>
>> One more test which is used to simulate access to temp tables under high
>> load.
>> I am using "upsert" into temp table in multiple connections.
>>
>> create global temp table gtemp (x integer primary key, y bigint);
>>
>> upsert.sql:
>> insert into gtemp values (random() * 1000000, 0) on conflict(x) do update
>> set y=gtemp.y+1;
>>
>> pgbench -c 10 -M prepared -T 100 -P 1 -n -f upsert.sql postgres
>>
>>
>> I failed to find some standard way in pgbech to perform per-session
>> initialization to create local temp table,
>> so I just insert this code in pgbench code:
>>
>> diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
>> index 570cf33..af6a431 100644
>> --- a/src/bin/pgbench/pgbench.c
>> +++ b/src/bin/pgbench/pgbench.c
>> @@ -5994,6 +5994,7 @@ threadRun(void *arg)
>> {
>> if ((state[i].con = doConnect()) == NULL)
>> goto done;
>> + executeStatement(state[i].con, "create temp table
>> ltemp(x integer primary key, y bigint)");
>> }
>> }
>>
>>
>> Results are the following:
>> Global temp table: 117526 TPS
>> Local temp table: 107802 TPS
>>
>>
>> So even for this workload global temp table with shared buffers are a
>> little bit faster.
>> I will be pleased if you can propose some other testing scenario.
>>
>
> please, try to increase number of connections.
>
>
> With 20 connections and 4 pgbench threads results are similar: 119k TPS
> for global temp tables and 115k TPS for local temp tables.
>
> I have tried yet another scenario: read-only access to temp tables:
>
> \set id random(1,10000000)
> select sum(y) from ltemp where x=:id;
>
> Tables are created and initialized in pgbench session startup:
>
> knizhnik@knizhnik:~/postgresql$ git diff
> diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
> index 570cf33..95295b0 100644
> --- a/src/bin/pgbench/pgbench.c
> +++ b/src/bin/pgbench/pgbench.c
> @@ -5994,6 +5994,8 @@ threadRun(void *arg)
> {
> if ((state[i].con = doConnect()) == NULL)
> goto done;
> + executeStatement(state[i].con, "create temp table
> ltemp(x integer primary key, y bigint)");
> + executeStatement(state[i].con, "insert into ltemp
> values (generate_series(1,1000000), generate_series(1,1000000))");
> }
> }
>
>
> Results for 10 connections with 10 million inserted records per table and
> 100 connections with 1 million inserted record per table :
>
> #connections:
> 10
> 100
> local temp
> 68k
> 90k
> global temp, shared_buffers=1G
> 63k
> 61k
> global temp, shared_buffers=10G 150k
> 150k
>
>
> So temporary tables with local buffers are slightly faster when data
> doesn't fit in shared buffers, but significantly slower when it fits.
>
>
>
> All previously reported results were produced at my desktop.
> I also run this read-only test on huge IBM server (POWER9, 2 NUMA nodes,
> 176 CPU, 1Tb RAM).
>
> Here the difference between local and global tables is not so large:
>
> Local temp: 739k TPS
> Global temp: 924k TPS
>
is not difference between local temp buffers and global temp buffers by too
low value of TEMP_BUFFERS?
Pavel
>
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-19 11:51 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-08-19 11:51 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
On 19.08.2019 14:25, Pavel Stehule wrote:
>
>
> po 19. 8. 2019 v 13:16 odesílatel Konstantin Knizhnik
> <k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> napsal:
>
>
>
> On 19.08.2019 11:51, Konstantin Knizhnik wrote:
>>
>>
>> On 18.08.2019 11:28, Pavel Stehule wrote:
>>>
>>>
>>> ne 18. 8. 2019 v 9:02 odesílatel Konstantin Knizhnik
>>> <k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>>
>>> napsal:
>>>
>>>
>>>
>>> On 16.08.2019 20:17, Pavel Stehule wrote:
>>>>
>>>>
>>>> pá 16. 8. 2019 v 16:12 odesílatel Konstantin Knizhnik
>>>> <k.knizhnik@postgrespro.ru
>>>> <mailto:k.knizhnik@postgrespro.ru>> napsal:
>>>>
>>>> I did more investigations of performance of global temp
>>>> tables with shared buffers vs. vanilla (local) temp tables.
>>>>
>>>> 1. Combination of persistent and temporary tables in
>>>> the same query.
>>>>
>>>> Preparation:
>>>> create table big(pk bigint primary key, val bigint);
>>>> insert into big values
>>>> (generate_series(1,100000000),generate_series(1,100000000));
>>>> create temp table lt(key bigint, count bigint);
>>>> create global temp table gt(key bigint, count bigint);
>>>>
>>>> Size of table is about 6Gb, I run this test on desktop
>>>> with 16GB of RAM and postgres with 1Gb shared buffers.
>>>> I run two queries:
>>>>
>>>> insert into T (select count(*),pk/P as key from big
>>>> group by key);
>>>> select sum(count) from T;
>>>>
>>>> where P is (100,10,1) and T is name of temp table (lt
>>>> or gt).
>>>> The table below contains times of both queries in msec:
>>>>
>>>> Percent of selected data
>>>> 1%
>>>> 10%
>>>> 100%
>>>> Local temp table
>>>> 44610
>>>> 90
>>>> 47920
>>>> 891
>>>> 63414
>>>> 21612
>>>> Global temp table
>>>> 44669
>>>> 35
>>>> 47939
>>>> 298
>>>> 59159
>>>> 26015
>>>>
>>>>
>>>> As you can see, time of insertion in temporary table is
>>>> almost the same
>>>> and time of traversal of temporary table is about twice
>>>> smaller for global temp table
>>>> when it fits in RAM together with persistent table and
>>>> slightly worser when it doesn't fit.
>>>>
>>>>
>>>>
>>>> 2. Temporary table only access.
>>>> The same system, but Postgres is configured with
>>>> shared_buffers=10GB, max_parallel_workers = 4,
>>>> max_parallel_workers_per_gather = 4
>>>>
>>>> Local temp tables:
>>>> create temp table local_temp(x1 bigint, x2 bigint, x3
>>>> bigint, x4 bigint, x5 bigint, x6 bigint, x7 bigint, x8
>>>> bigint, x9 bigint);
>>>> insert into local_temp values
>>>> (generate_series(1,100000000),0,0,0,0,0,0,0,0);
>>>> select sum(x1) from local_temp;
>>>>
>>>> Global temp tables:
>>>> create global temporary table global_temp(x1 bigint, x2
>>>> bigint, x3 bigint, x4 bigint, x5 bigint, x6 bigint, x7
>>>> bigint, x8 bigint, x9 bigint);
>>>> insert into global_temp values
>>>> (generate_series(1,100000000),0,0,0,0,0,0,0,0);
>>>> select sum(x1) from global_temp;
>>>>
>>>> Results (msec):
>>>>
>>>> Insert
>>>> Select
>>>> Local temp table 37489
>>>> 48322
>>>> Global temp table 44358
>>>> 3003
>>>>
>>>>
>>>> So insertion in local temp table is performed slightly
>>>> faster but select is 16 times slower!
>>>>
>>>> Conclusion:
>>>> In the assumption then temp table fits in memory,
>>>> global temp tables with shared buffers provides better
>>>> performance than local temp table.
>>>> I didn't consider here global temp tables with local
>>>> buffers because for them results should be similar with
>>>> local temp tables.
>>>>
>>>>
>>>> Probably there is not a reason why shared buffers should be
>>>> slower than local buffers when system is under low load.
>>>>
>>>> access to shared memory is protected by spin locks (are
>>>> cheap for few processes), so tests in one or few process
>>>> are not too important (or it is just one side of space)
>>>>
>>>> another topic can be performance on MS Sys - there are
>>>> stories about not perfect performance of shared memory there.
>>>>
>>>> Regards
>>>>
>>>> Pavel
>>>>
>>> One more test which is used to simulate access to temp
>>> tables under high load.
>>> I am using "upsert" into temp table in multiple connections.
>>>
>>> create global temp table gtemp (x integer primary key, y
>>> bigint);
>>>
>>> upsert.sql:
>>> insert into gtemp values (random() * 1000000, 0) on
>>> conflict(x) do update set y=gtemp.y+1;
>>>
>>> pgbench -c 10 -M prepared -T 100 -P 1 -n -f upsert.sql postgres
>>>
>>>
>>> I failed to find some standard way in pgbech to perform
>>> per-session initialization to create local temp table,
>>> so I just insert this code in pgbench code:
>>>
>>> diff --git a/src/bin/pgbench/pgbench.c
>>> b/src/bin/pgbench/pgbench.c
>>> index 570cf33..af6a431 100644
>>> --- a/src/bin/pgbench/pgbench.c
>>> +++ b/src/bin/pgbench/pgbench.c
>>> @@ -5994,6 +5994,7 @@ threadRun(void *arg)
>>> {
>>> if ((state[i].con = doConnect()) ==
>>> NULL)
>>> goto done;
>>> + executeStatement(state[i].con, "create temp table ltemp(x
>>> integer primary key, y bigint)");
>>> }
>>> }
>>>
>>>
>>> Results are the following:
>>> Global temp table: 117526 TPS
>>> Local temp table: 107802 TPS
>>>
>>>
>>> So even for this workload global temp table with shared
>>> buffers are a little bit faster.
>>> I will be pleased if you can propose some other testing
>>> scenario.
>>>
>>>
>>> please, try to increase number of connections.
>>
>> With 20 connections and 4 pgbench threads results are similar:
>> 119k TPS for global temp tables and 115k TPS for local temp tables.
>>
>> I have tried yet another scenario: read-only access to temp tables:
>>
>> \set id random(1,10000000)
>> select sum(y) from ltemp where x=:id;
>>
>> Tables are created and initialized in pgbench session startup:
>>
>> knizhnik@knizhnik:~/postgresql$ git diff
>> diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
>> index 570cf33..95295b0 100644
>> --- a/src/bin/pgbench/pgbench.c
>> +++ b/src/bin/pgbench/pgbench.c
>> @@ -5994,6 +5994,8 @@ threadRun(void *arg)
>> {
>> if ((state[i].con = doConnect()) == NULL)
>> goto done;
>> + executeStatement(state[i].con, "create
>> temp table ltemp(x integer primary key, y bigint)");
>> + executeStatement(state[i].con, "insert
>> into ltemp values (generate_series(1,1000000),
>> generate_series(1,1000000))");
>> }
>> }
>>
>>
>> Results for 10 connections with 10 million inserted records per
>> table and 100 connections with 1 million inserted record per table :
>>
>> #connections:
>> 10
>> 100
>> local temp
>> 68k
>> 90k
>> global temp, shared_buffers=1G
>> 63k
>> 61k
>> global temp, shared_buffers=10G 150k
>> 150k
>>
>>
>>
>> So temporary tables with local buffers are slightly faster when
>> data doesn't fit in shared buffers, but significantly slower when
>> it fits.
>>
>>
>
> All previously reported results were produced at my desktop.
> I also run this read-only test on huge IBM server (POWER9, 2 NUMA
> nodes, 176 CPU, 1Tb RAM).
>
> Here the difference between local and global tables is not so large:
>
> Local temp: 739k TPS
> Global temp: 924k TPS
>
>
> is not difference between local temp buffers and global temp buffers
> by too low value of TEMP_BUFFERS?
Certainly, default (small) temp buffer size plays roles.
But it this IPC host this difference is not so important.
Result with local temp tables and temp_buffers = 1GB: 859k TPS.
> --
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-19 15:53 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2019-08-19 15:53 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
> Certainly, default (small) temp buffer size plays roles.
> But it this IPC host this difference is not so important.
> Result with local temp tables and temp_buffers = 1GB: 859k TPS.
>
It is little bit unexpected result.I understand so it partially it is
generic problem access to smaller dedicated caches versus access to bigger
shared cache.
But it is hard to imagine so access to local cache is 10% slower than
access to shared cache. Maybe there is some bottle neck - maybe our
implementation of local buffers are suboptimal.
Using local buffers for global temporary tables can be interesting from
another reason - it uses temporary files, and temporary files can be
forwarded on ephemeral IO on Amazon cloud (with much better performance
than persistent IO).
>
> --
>
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-20 14:51 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-08-20 14:51 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
On 19.08.2019 18:53, Pavel Stehule wrote:
>
>
>
> Certainly, default (small) temp buffer size plays roles.
> But it this IPC host this difference is not so important.
> Result with local temp tables and temp_buffers = 1GB: 859k TPS.
>
>
> It is little bit unexpected result.I understand so it partially it is
> generic problem access to smaller dedicated caches versus access to
> bigger shared cache.
>
> But it is hard to imagine so access to local cache is 10% slower than
> access to shared cache. Maybe there is some bottle neck - maybe our
> implementation of local buffers are suboptimal.
It may be caused by system memory allocator - in case of using shared
buffers we do not need to ask OS to allocate more memory.
>
> Using local buffers for global temporary tables can be interesting
> from another reason - it uses temporary files, and temporary files can
> be forwarded on ephemeral IO on Amazon cloud (with much better
> performance than persistent IO).
>
>
My assumption is that temporary tables almost always fit in memory. So
in most cases there is on need to write data to file at all.
As I wrote at the beginning of this thread, one of the problems with
temporary table sis that it is not possible to use them at replica.
Global temp tables allows to share metadata between master and replica.
I perform small investigation: how difficult it will be to support
inserts in temp tables at replica.
First my impression was that it can be done in tricky but simple way.
By making small changes changing just three places:
1. Prohibit non-select statements in read-only transactions
2. Xid assignment (return FrozenTransactionId)
3. Transaction commit/abort
I managed to provide normal work with global temp tables at replica.
But there is one problem with this approach: it is not possible to undo
changes in temp tables so rollback doesn't work.
I tried another solution, but assigning some dummy Xids to standby
transactions.
But this approach require much more changes:
- Initialize page for such transaction in CLOG
- Mark transaction as committed/aborted in XCLOG
- Change snapshot check in visibility function
And still I didn't find safe way to cleanup CLOG space.
Alternative solution is to implement "local CLOG" for such transactions.
The straightforward solution is to use hashtable. But it may cause
memory overflow if we have long living backend which performs huge
number of transactions.
Also in this case we need to change visibility check functions.
So I have implemented simplest solution with frozen xid and force
backend termination in case of transaction rollback (so user will no see
inconsistent behavior).
Attached please find global_private_temp_replica.patch which implements
this approach.
It will be nice if somebody can suggest better solution for temporary
tables at replica.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
[text/x-patch] global_private_temp_replica.patch (55.0K, ../../c504633d-1658-8cf2-3446-d031f9b8b2c3@postgrespro.ru/3-global_private_temp_replica.patch)
download | inline diff:
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 9726020..c99701d 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/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f1ff01e..e92d324 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 9c1f7de..97cc9e4 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/transam/varsup.c b/src/backend/access/transam/varsup.c
index 5b759ec..fda7573 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -69,9 +69,10 @@ GetNewTransactionId(bool isSubXact)
return FullTransactionIdFromEpochAndXid(0, BootstrapTransactionId);
}
- /* safety check, we should never get this far in a HS standby */
+ /* Make it possible to access global temporary tables at standby */
if (RecoveryInProgress())
- elog(ERROR, "cannot assign TransactionIds during recovery");
+ return FullTransactionIdFromEpochAndXid(0, FrozenTransactionId);
+ /* elog(ERROR, "cannot assign TransactionIds during recovery"); */
LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 1bbaeee..9857f1c 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -1206,7 +1206,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 +1624,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)
@@ -2991,6 +2991,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;
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index a065419..8814afb 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -409,6 +409,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 3e1d406..aaa2c49 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3590,7 +3590,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 3cc886f..a111ddc 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 cedb4ee..d11c5b3 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 0960b33..6c3998f 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 fb2be10..a31f775 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -586,7 +586,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);
@@ -7678,6 +7679,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,
@@ -14082,6 +14089,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 */
@@ -14569,14 +14583,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/executor/execMain.c b/src/backend/executor/execMain.c
index dbd7dd9..efe6f21 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -788,6 +788,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
if (isTempNamespace(get_rel_namespace(rte->relid)))
continue;
+ if (get_rel_persistence(rte->relid) == RELPERSISTENCE_SESSION)
+ continue;
+
PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 98e9948..1a9170b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -124,7 +124,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
relation = table_open(relationObjectId, NoLock);
/* Temporary and unlogged relations are inaccessible during recovery. */
- if (!RelationNeedsWAL(relation) && RecoveryInProgress())
+ if (!RelationNeedsWAL(relation) && RecoveryInProgress() && relation->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot access temporary or unlogged relations during recovery")));
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c97bb36..f9b2000 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3265,20 +3265,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 6e5768c..ea6989b 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 073f313..3383c35 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2069,7 +2069,7 @@ do_autovacuum(void)
* Check if it is a temp table (presumably, of some other backend's).
* We cannot safely process other backends' temp tables.
*/
- if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+ if (IsLocalRelpersistence(classForm->relpersistence))
{
/*
* We just ignore it if the owning backend is still active and
@@ -2154,7 +2154,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 2488607..86e8fca 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 0cc9ede..1dff0c8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15593,8 +15593,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 b0fe19e..dfa2044 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -328,6 +328,17 @@ 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
@@ -524,7 +535,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 69ae227..95919f8 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -87,3 +87,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;
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-20 16:06 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2019-08-20 16:06 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
út 20. 8. 2019 v 16:51 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:
>
>
> On 19.08.2019 18:53, Pavel Stehule wrote:
>
>
>
>
>> Certainly, default (small) temp buffer size plays roles.
>> But it this IPC host this difference is not so important.
>> Result with local temp tables and temp_buffers = 1GB: 859k TPS.
>>
>
> It is little bit unexpected result.I understand so it partially it is
> generic problem access to smaller dedicated caches versus access to bigger
> shared cache.
>
> But it is hard to imagine so access to local cache is 10% slower than
> access to shared cache. Maybe there is some bottle neck - maybe our
> implementation of local buffers are suboptimal.
>
>
> It may be caused by system memory allocator - in case of using shared
> buffers we do not need to ask OS to allocate more memory.
>
maybe, but shared buffers you have a overhead with searching free buffers
and some overhead with synchronization processes.
>
>
> Using local buffers for global temporary tables can be interesting from
> another reason - it uses temporary files, and temporary files can be
> forwarded on ephemeral IO on Amazon cloud (with much better performance
> than persistent IO).
>
>
>
> My assumption is that temporary tables almost always fit in memory. So in
> most cases there is on need to write data to file at all.
>
>
> As I wrote at the beginning of this thread, one of the problems with
> temporary table sis that it is not possible to use them at replica.
> Global temp tables allows to share metadata between master and replica.
>
I am not sure if I understand to last sentence. Global temp tables should
be replicated on replica servers. But the content should not be replicated.
This should be session specific.
> I perform small investigation: how difficult it will be to support inserts
> in temp tables at replica.
> First my impression was that it can be done in tricky but simple way.
>
> By making small changes changing just three places:
> 1. Prohibit non-select statements in read-only transactions
> 2. Xid assignment (return FrozenTransactionId)
> 3. Transaction commit/abort
>
> I managed to provide normal work with global temp tables at replica.
> But there is one problem with this approach: it is not possible to undo
> changes in temp tables so rollback doesn't work.
>
> I tried another solution, but assigning some dummy Xids to standby
> transactions.
> But this approach require much more changes:
> - Initialize page for such transaction in CLOG
> - Mark transaction as committed/aborted in XCLOG
> - Change snapshot check in visibility function
>
> And still I didn't find safe way to cleanup CLOG space.
> Alternative solution is to implement "local CLOG" for such transactions.
> The straightforward solution is to use hashtable. But it may cause memory
> overflow if we have long living backend which performs huge number of
> transactions.
> Also in this case we need to change visibility check functions.
>
> So I have implemented simplest solution with frozen xid and force backend
> termination in case of transaction rollback (so user will no see
> inconsistent behavior).
> Attached please find global_private_temp_replica.patch which implements
> this approach.
> It will be nice if somebody can suggest better solution for temporary
> tables at replica.
>
This is another hard issue. Probably backend temination should be
acceptable solution. I don't understand well to this area, but if replica
allows writing (to global temp tables), then replica have to have local
CLOG.
CLOG for global temp tables can be more simple then standard CLOG. Data are
not shared, and life of data (and number of transactions) can be low.
Another solution is wait on ZHeap storage and replica can to have own UNDO
log.
>
>
>
>
>
>
>
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-20 16:42 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-08-20 16:42 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
On 20.08.2019 19:06, Pavel Stehule wrote:
>
>
> As I wrote at the beginning of this thread, one of the problems
> with temporary table sis that it is not possible to use them at
> replica.
> Global temp tables allows to share metadata between master and
> replica.
>
>
> I am not sure if I understand to last sentence. Global temp tables
> should be replicated on replica servers. But the content should not be
> replicated. This should be session specific.
Obviously.
When we run OLAP queries at replica, it will be great if we can do
insert into temp_table (select ...);
With local temp tables it is not possible just because you can not
create temp table at replica.
But global temp table can be created at master and populated with data
at replica.
> I perform small investigation: how difficult it will be to support
> inserts in temp tables at replica.
> First my impression was that it can be done in tricky but simple way.
>
> By making small changes changing just three places:
> 1. Prohibit non-select statements in read-only transactions
> 2. Xid assignment (return FrozenTransactionId)
> 3. Transaction commit/abort
>
> I managed to provide normal work with global temp tables at replica.
> But there is one problem with this approach: it is not possible to
> undo changes in temp tables so rollback doesn't work.
>
> I tried another solution, but assigning some dummy Xids to standby
> transactions.
> But this approach require much more changes:
> - Initialize page for such transaction in CLOG
> - Mark transaction as committed/aborted in XCLOG
> - Change snapshot check in visibility function
>
> And still I didn't find safe way to cleanup CLOG space.
> Alternative solution is to implement "local CLOG" for such
> transactions.
> The straightforward solution is to use hashtable. But it may cause
> memory overflow if we have long living backend which performs huge
> number of transactions.
> Also in this case we need to change visibility check functions.
>
> So I have implemented simplest solution with frozen xid and force
> backend termination in case of transaction rollback (so user will
> no see inconsistent behavior).
> Attached please find global_private_temp_replica.patch which
> implements this approach.
> It will be nice if somebody can suggest better solution for
> temporary tables at replica.
>
>
> This is another hard issue. Probably backend temination should be
> acceptable solution. I don't understand well to this area, but if
> replica allows writing (to global temp tables), then replica have to
> have local CLOG.
There are several problems:
1. How to choose XID for writing transaction at standby. The simplest
solution is to just add 0x7fffffff to the current XID.
It eliminates possibility of conflict with normal XIDs (received from
master).
But requires changes in visibility functions. Visibility check function
do not know OID of tuple owner, just XID stored in the tuple header. It
should make a decision just based on this XID.
2. How to perform cleanup of not needed XIDs. Right now there is quite
complex logic of how to free CLOG pages.
3. How to implement visibility rules to such XIDs.
>
> CLOG for global temp tables can be more simple then standard CLOG.
> Data are not shared, and life of data (and number of transactions) can
> be low.
>
> Another solution is wait on ZHeap storage and replica can to have own
> UNDO log.
>
I thought about implementation of special table access method for
temporary tables.
I am trying to understand now if it is the only possible approach or
there are simpler solutions.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-20 17:01 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2019-08-20 17:01 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
út 20. 8. 2019 v 18:42 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:
>
>
> On 20.08.2019 19:06, Pavel Stehule wrote:
>
>
>
> As I wrote at the beginning of this thread, one of the problems with
>> temporary table sis that it is not possible to use them at replica.
>> Global temp tables allows to share metadata between master and replica.
>>
>
> I am not sure if I understand to last sentence. Global temp tables should
> be replicated on replica servers. But the content should not be replicated.
> This should be session specific.
>
>
> Obviously.
> When we run OLAP queries at replica, it will be great if we can do
>
> insert into temp_table (select ...);
>
> With local temp tables it is not possible just because you can not create
> temp table at replica.
> But global temp table can be created at master and populated with data at
> replica.
>
yes
>
>
>> I perform small investigation: how difficult it will be to support
>> inserts in temp tables at replica.
>> First my impression was that it can be done in tricky but simple way.
>>
>> By making small changes changing just three places:
>> 1. Prohibit non-select statements in read-only transactions
>> 2. Xid assignment (return FrozenTransactionId)
>> 3. Transaction commit/abort
>>
>> I managed to provide normal work with global temp tables at replica.
>> But there is one problem with this approach: it is not possible to undo
>> changes in temp tables so rollback doesn't work.
>>
>> I tried another solution, but assigning some dummy Xids to standby
>> transactions.
>> But this approach require much more changes:
>> - Initialize page for such transaction in CLOG
>> - Mark transaction as committed/aborted in XCLOG
>> - Change snapshot check in visibility function
>>
>> And still I didn't find safe way to cleanup CLOG space.
>> Alternative solution is to implement "local CLOG" for such transactions.
>> The straightforward solution is to use hashtable. But it may cause memory
>> overflow if we have long living backend which performs huge number of
>> transactions.
>> Also in this case we need to change visibility check functions.
>>
>> So I have implemented simplest solution with frozen xid and force backend
>> termination in case of transaction rollback (so user will no see
>> inconsistent behavior).
>> Attached please find global_private_temp_replica.patch which implements
>> this approach.
>> It will be nice if somebody can suggest better solution for temporary
>> tables at replica.
>>
>
> This is another hard issue. Probably backend temination should be
> acceptable solution. I don't understand well to this area, but if replica
> allows writing (to global temp tables), then replica have to have local
> CLOG.
>
>
> There are several problems:
>
> 1. How to choose XID for writing transaction at standby. The simplest
> solution is to just add 0x7fffffff to the current XID.
> It eliminates possibility of conflict with normal XIDs (received from
> master).
> But requires changes in visibility functions. Visibility check function do
> not know OID of tuple owner, just XID stored in the tuple header. It should
> make a decision just based on this XID.
>
> 2. How to perform cleanup of not needed XIDs. Right now there is quite
> complex logic of how to free CLOG pages.
>
> 3. How to implement visibility rules to such XIDs.
>
in theory every session can have own CLOG. When you finish session, you can
truncate this file.
>
>
> CLOG for global temp tables can be more simple then standard CLOG. Data
> are not shared, and life of data (and number of transactions) can be low.
>
> Another solution is wait on ZHeap storage and replica can to have own UNDO
> log.
>
> I thought about implementation of special table access method for
> temporary tables.
>
+1
> I am trying to understand now if it is the only possible approach or
> there are simpler solutions.
>
>
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-08-21 08:54 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-08-21 08:54 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
On 20.08.2019 20:01, Pavel Stehule wrote:
> Another solution is wait on ZHeap storage and replica can to have own
> UNDO log.
>
>>
> I thought about implementation of special table access method for
> temporary tables.
>
>
> +1
>
Unfortunately implementing special table access method for temporary
tables doesn't solve all problems.
XID generation is not part of table access methods.
So we still need to assign some XID to write transaction at replica
which will not conflict with XIDs received from master.
Actually only global temp tables can be updated at replica and so
assigned XIDs can be stored only in tuples of such relations.
But still I am not sure that we can use arbitrary XID for such
transactions at replica.
Also I upset by amount of functionality which has to be reimplemented
for global temp tables if we really want to provide access method for them:
1. CLOG
2. vacuum
3. MVCC visibility
And still it is not possible to encapsulate all changes need to support
writes to temp tables at replica inside table access method.
XID assignment, transaction commit and abort, subtransactions - all this
places need to be patched.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-09-18 10:04 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 2 replies; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-09-18 10:04 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
On 21.08.2019 11:54, Konstantin Knizhnik wrote:
>
>
> On 20.08.2019 20:01, Pavel Stehule wrote:
>> Another solution is wait on ZHeap storage and replica can to have own
>> UNDO log.
>>
>>>
>> I thought about implementation of special table access method for
>> temporary tables.
>>
>>
>> +1
>>
> Unfortunately implementing special table access method for temporary
> tables doesn't solve all problems.
> XID generation is not part of table access methods.
> So we still need to assign some XID to write transaction at replica
> which will not conflict with XIDs received from master.
> Actually only global temp tables can be updated at replica and so
> assigned XIDs can be stored only in tuples of such relations.
> But still I am not sure that we can use arbitrary XID for such
> transactions at replica.
>
> Also I upset by amount of functionality which has to be reimplemented
> for global temp tables if we really want to provide access method for
> them:
>
> 1. CLOG
> 2. vacuum
> 3. MVCC visibility
>
> And still it is not possible to encapsulate all changes need to
> support writes to temp tables at replica inside table access method.
> XID assignment, transaction commit and abort, subtransactions - all
> this places need to be patched.
>
I was able to fully support work with global temp tables at replica
(including subtransactions).
The patch is attached. Also you can find this version in
https://github.com/postgrespro/postgresql.builtin_pool/tree/global_temp_hot
Right now transactions at replica updating global temp table are
assigned special kind of GIDs which are not related with XIDs received
from master.
So special visibility rules are used for such tables at replica. Also I
have to patch TransactionIdIsInProgress, TransactionIdDidCommit,
TransactionIdGetCurrent
functions to correctly handle such XIDs. In principle it is possible to
implement global temp tables as special heap access method. But it will
require copying a lot of code (heapam.c)
so I prefer to add few checks to existed functions.
There are still some limitations:
- Number of transactions at replica which update temp tables is limited
by 2^32 (wraparound problem is not addressed).
- I have to maintain in-memory analog of CLOG for such transactions
which is also not cropped. It means that for 2^32 transaction size of
bitmap can grow up to 0.5Gb.
I try to understand what are the following steps in global temp tables
support.
This is why I want to perform short survey - what people are expecting
from global temp tables:
1. I do not need them at all.
2. Eliminate catalog bloating.
3. Mostly needed for compatibility with Oracle (simplify porting,...).
4. Parallel query execution.
5. Can be used at replica.
6. More efficient use of resources (first of all memory).
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
[text/x-patch] global_shared_temp_replica.patch (106.9K, ../../f4e875e4-8457-e420-47bc-3cc19f942a93@postgrespro.ru/3-global_shared_temp_replica.patch)
download | inline diff:
diff --git a/contrib/pg_buffercache/pg_buffercache_pages.c b/contrib/pg_buffercache/pg_buffercache_pages.c
index 1bd579f..2d93f6f 100644
--- a/contrib/pg_buffercache/pg_buffercache_pages.c
+++ b/contrib/pg_buffercache/pg_buffercache_pages.c
@@ -153,9 +153,9 @@ pg_buffercache_pages(PG_FUNCTION_ARGS)
buf_state = LockBufHdr(bufHdr);
fctx->record[i].bufferid = BufferDescriptorGetBuffer(bufHdr);
- fctx->record[i].relfilenode = bufHdr->tag.rnode.relNode;
- fctx->record[i].reltablespace = bufHdr->tag.rnode.spcNode;
- fctx->record[i].reldatabase = bufHdr->tag.rnode.dbNode;
+ fctx->record[i].relfilenode = bufHdr->tag.rnode.node.relNode;
+ fctx->record[i].reltablespace = bufHdr->tag.rnode.node.spcNode;
+ fctx->record[i].reldatabase = bufHdr->tag.rnode.node.dbNode;
fctx->record[i].forknum = bufHdr->tag.forkNum;
fctx->record[i].blocknum = bufHdr->tag.blockNum;
fctx->record[i].usagecount = BUF_STATE_GET_USAGECOUNT(buf_state);
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 38ae240..8a04954 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -608,9 +608,9 @@ apw_dump_now(bool is_bgworker, bool dump_unlogged)
if (buf_state & BM_TAG_VALID &&
((buf_state & BM_PERMANENT) || dump_unlogged))
{
- block_info_array[num_blocks].database = bufHdr->tag.rnode.dbNode;
- block_info_array[num_blocks].tablespace = bufHdr->tag.rnode.spcNode;
- block_info_array[num_blocks].filenode = bufHdr->tag.rnode.relNode;
+ block_info_array[num_blocks].database = bufHdr->tag.rnode.node.dbNode;
+ block_info_array[num_blocks].tablespace = bufHdr->tag.rnode.node.spcNode;
+ block_info_array[num_blocks].filenode = bufHdr->tag.rnode.node.relNode;
block_info_array[num_blocks].forknum = bufHdr->tag.forkNum;
block_info_array[num_blocks].blocknum = bufHdr->tag.blockNum;
++num_blocks;
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index a2c44a9..43b4c66 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -158,7 +158,8 @@ pgrowlocks(PG_FUNCTION_ARGS)
/* must hold a buffer lock to call HeapTupleSatisfiesUpdate */
LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
- htsu = HeapTupleSatisfiesUpdate(tuple,
+ htsu = HeapTupleSatisfiesUpdate(mydata->rel,
+ tuple,
GetCurrentCommandId(false),
hscan->rs_cbuf);
xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 70af43e..9cce720 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -349,7 +349,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
/* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
- if (HeapTupleSatisfiesVisibility(tuple, &SnapshotDirty, hscan->rs_cbuf))
+ if (HeapTupleSatisfiesVisibility(rel, tuple, &SnapshotDirty, hscan->rs_cbuf))
{
stat.tuple_len += tuple->t_len;
stat.tuple_count++;
diff --git a/src/backend/access/gin/ginxlog.c b/src/backend/access/gin/ginxlog.c
index c945b28..14d4e48 100644
--- a/src/backend/access/gin/ginxlog.c
+++ b/src/backend/access/gin/ginxlog.c
@@ -95,13 +95,13 @@ ginRedoInsertEntry(Buffer buffer, bool isLeaf, BlockNumber rightblkno, void *rda
if (PageAddItem(page, (Item) itup, IndexTupleSize(itup), offset, false, false) == InvalidOffsetNumber)
{
- RelFileNode node;
+ RelFileNodeBackend rnode;
ForkNumber forknum;
BlockNumber blknum;
- BufferGetTag(buffer, &node, &forknum, &blknum);
+ BufferGetTag(buffer, &rnode, &forknum, &blknum);
elog(ERROR, "failed to add item to index page in %u/%u/%u",
- node.spcNode, node.dbNode, node.relNode);
+ rnode.node.spcNode, rnode.node.dbNode, rnode.node.relNode);
}
}
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 9726020..c99701d 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/heap/heapam.c b/src/backend/access/heap/heapam.c
index 9430994..181efde 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);
@@ -1612,7 +1613,7 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
ItemPointerSet(&(heapTuple->t_self), BufferGetBlockNumber(buffer), offnum);
/* 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);
/* reset to original, non-redirected, tid */
@@ -1754,7 +1755,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;
@@ -1851,6 +1852,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
*
@@ -1873,7 +1882,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;
@@ -2110,7 +2119,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;
@@ -2449,7 +2458,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;
@@ -2514,7 +2523,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)
{
@@ -2633,7 +2642,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;
}
@@ -2900,7 +2909,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;
@@ -3070,7 +3079,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);
@@ -3262,7 +3271,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));
@@ -4018,7 +4027,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)
{
@@ -4193,7 +4202,7 @@ l3:
TM_Result res;
res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
- GetCurrentTransactionId(),
+ GetTransactionId(relation),
mode);
if (res != TM_Ok)
{
@@ -4441,7 +4450,7 @@ l3:
TM_Result res;
res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
- GetCurrentTransactionId(),
+ GetTransactionId(relation),
mode);
if (res != TM_Ok)
{
@@ -4550,7 +4559,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();
@@ -5570,7 +5579,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 09bc6fe..a189834 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);
@@ -671,6 +672,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);
@@ -2160,7 +2162,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;
@@ -2480,7 +2482,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/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 5962126..bdb6c95 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/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/varsup.c b/src/backend/access/transam/varsup.c
index 5b759ec..388faae 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -71,7 +71,7 @@ GetNewTransactionId(bool isSubXact)
/* safety check, we should never get this far in a HS standby */
if (RecoveryInProgress())
- elog(ERROR, "cannot assign TransactionIds during recovery");
+ elog(ERROR, "cannot assign TransactionIds during recovery");
LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 1bbaeee..ab1bef9 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;
@@ -4856,6 +4922,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();
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index 3ec67d4..edec8ca 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -213,6 +213,7 @@ void
XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
{
registered_buffer *regbuf;
+ RelFileNodeBackend rnode;
/* NO_IMAGE doesn't make sense with FORCE_IMAGE */
Assert(!((flags & REGBUF_FORCE_IMAGE) && (flags & (REGBUF_NO_IMAGE))));
@@ -227,7 +228,8 @@ XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
regbuf = ®istered_buffers[block_id];
- BufferGetTag(buffer, ®buf->rnode, ®buf->forkno, ®buf->block);
+ BufferGetTag(buffer, &rnode, ®buf->forkno, ®buf->block);
+ regbuf->rnode = rnode.node;
regbuf->page = BufferGetPage(buffer);
regbuf->flags = flags;
regbuf->rdata_tail = (XLogRecData *) ®buf->rdata_head;
@@ -919,7 +921,7 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std)
int flags;
PGAlignedBlock copied_buffer;
char *origdata = (char *) BufferGetBlock(buffer);
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forkno;
BlockNumber blkno;
@@ -948,7 +950,7 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std)
flags |= REGBUF_STANDARD;
BufferGetTag(buffer, &rnode, &forkno, &blkno);
- XLogRegisterBlock(0, &rnode, forkno, blkno, copied_buffer.data, flags);
+ XLogRegisterBlock(0, &rnode.node, forkno, blkno, copied_buffer.data, flags);
recptr = XLogInsert(RM_XLOG_ID, XLOG_FPI_FOR_HINT);
}
@@ -1009,7 +1011,7 @@ XLogRecPtr
log_newpage_buffer(Buffer buffer, bool page_std)
{
Page page = BufferGetPage(buffer);
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forkNum;
BlockNumber blkno;
@@ -1018,7 +1020,7 @@ log_newpage_buffer(Buffer buffer, bool page_std)
BufferGetTag(buffer, &rnode, &forkNum, &blkno);
- return log_newpage(&rnode, forkNum, blkno, page, page_std);
+ return log_newpage(&rnode.node, forkNum, blkno, page, page_std);
}
/*
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index a065419..8814afb 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -409,6 +409,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 99ae159..24b2438 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3612,7 +3612,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 3cc886f..a111ddc 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 cedb4ee..d11c5b3 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 0960b33..6c3998f 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 fb2be10..a31f775 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -586,7 +586,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);
@@ -7678,6 +7679,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,
@@ -14082,6 +14089,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 */
@@ -14569,14 +14583,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/executor/execMain.c b/src/backend/executor/execMain.c
index dbd7dd9..efe6f21 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -788,6 +788,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
if (isTempNamespace(get_rel_namespace(rte->relid)))
continue;
+ if (get_rel_persistence(rte->relid) == RELPERSISTENCE_SESSION)
+ continue;
+
PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 98e9948..1a9170b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -124,7 +124,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
relation = table_open(relationObjectId, NoLock);
/* Temporary and unlogged relations are inaccessible during recovery. */
- if (!RelationNeedsWAL(relation) && RecoveryInProgress())
+ if (!RelationNeedsWAL(relation) && RecoveryInProgress() && relation->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot access temporary or unlogged relations during recovery")));
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c97bb36..f9b2000 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3265,20 +3265,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 a9b2f8b..2f261b9 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 073f313..5760a9c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2154,7 +2154,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/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index e8ffa04..2004d2f 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -3483,6 +3483,7 @@ ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
{
ReorderBufferTupleCidKey key;
ReorderBufferTupleCidEnt *ent;
+ RelFileNodeBackend rnode;
ForkNumber forkno;
BlockNumber blockno;
bool updated_mapping = false;
@@ -3496,7 +3497,8 @@ ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
* get relfilenode from the buffer, no convenient way to access it other
* than that.
*/
- BufferGetTag(buffer, &key.relnode, &forkno, &blockno);
+ BufferGetTag(buffer, &rnode, &forkno, &blockno);
+ key.relnode = rnode.node;
/* tuples can only be in the main fork */
Assert(forkno == MAIN_FORKNUM);
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 6f3a402..76ce953 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -556,7 +556,7 @@ PrefetchBuffer(Relation reln, ForkNumber forkNum, BlockNumber blockNum)
int buf_id;
/* create a tag so we can lookup the buffer */
- INIT_BUFFERTAG(newTag, reln->rd_smgr->smgr_rnode.node,
+ INIT_BUFFERTAG(newTag, reln->rd_smgr->smgr_rnode,
forkNum, blockNum);
/* determine its hash code and partition lock ID */
@@ -710,7 +710,7 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
Block bufBlock;
bool found;
bool isExtend;
- bool isLocalBuf = SmgrIsTemp(smgr);
+ bool isLocalBuf = SmgrIsTemp(smgr) && relpersistence == RELPERSISTENCE_TEMP;
*hit = false;
@@ -1010,7 +1010,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
uint32 buf_state;
/* create a tag so we can lookup the buffer */
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* determine its hash code and partition lock ID */
newHash = BufTableHashCode(&newTag);
@@ -1532,7 +1532,8 @@ ReleaseAndReadBuffer(Buffer buffer,
{
bufHdr = GetLocalBufferDescriptor(-buffer - 1);
if (bufHdr->tag.blockNum == blockNum &&
- RelFileNodeEquals(bufHdr->tag.rnode, relation->rd_node) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, relation->rd_node) &&
+ bufHdr->tag.rnode.backend == relation->rd_backend &&
bufHdr->tag.forkNum == forkNum)
return buffer;
ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
@@ -1543,7 +1544,8 @@ ReleaseAndReadBuffer(Buffer buffer,
bufHdr = GetBufferDescriptor(buffer - 1);
/* we have pin, so it's ok to examine tag without spinlock */
if (bufHdr->tag.blockNum == blockNum &&
- RelFileNodeEquals(bufHdr->tag.rnode, relation->rd_node) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, relation->rd_node) &&
+ bufHdr->tag.rnode.backend == relation->rd_backend &&
bufHdr->tag.forkNum == forkNum)
return buffer;
UnpinBuffer(bufHdr, true);
@@ -1845,8 +1847,8 @@ BufferSync(int flags)
item = &CkptBufferIds[num_to_scan++];
item->buf_id = buf_id;
- item->tsId = bufHdr->tag.rnode.spcNode;
- item->relNode = bufHdr->tag.rnode.relNode;
+ item->tsId = bufHdr->tag.rnode.node.spcNode;
+ item->relNode = bufHdr->tag.rnode.node.relNode;
item->forkNum = bufHdr->tag.forkNum;
item->blockNum = bufHdr->tag.blockNum;
}
@@ -2559,7 +2561,7 @@ PrintBufferLeakWarning(Buffer buffer)
}
/* theoretically we should lock the bufhdr here */
- path = relpathbackend(buf->tag.rnode, backend, buf->tag.forkNum);
+ path = relpathbackend(buf->tag.rnode.node, backend, buf->tag.forkNum);
buf_state = pg_atomic_read_u32(&buf->state);
elog(WARNING,
"buffer refcount leak: [%03d] "
@@ -2631,7 +2633,7 @@ BufferGetBlockNumber(Buffer buffer)
* a buffer.
*/
void
-BufferGetTag(Buffer buffer, RelFileNode *rnode, ForkNumber *forknum,
+BufferGetTag(Buffer buffer, RelFileNodeBackend *rnode, ForkNumber *forknum,
BlockNumber *blknum)
{
BufferDesc *bufHdr;
@@ -2696,7 +2698,7 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
/* Find smgr relation for buffer */
if (reln == NULL)
- reln = smgropen(buf->tag.rnode, InvalidBackendId);
+ reln = smgropen(buf->tag.rnode.node, buf->tag.rnode.backend);
TRACE_POSTGRESQL_BUFFER_FLUSH_START(buf->tag.forkNum,
buf->tag.blockNum,
@@ -2930,7 +2932,7 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber forkNum,
int i;
/* If it's a local relation, it's localbuf.c's problem. */
- if (RelFileNodeBackendIsTemp(rnode))
+ if (RelFileNodeBackendIsLocalTemp(rnode))
{
if (rnode.backend == MyBackendId)
DropRelFileNodeLocalBuffers(rnode.node, forkNum, firstDelBlock);
@@ -2958,11 +2960,11 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber forkNum,
* We could check forkNum and blockNum as well as the rnode, but the
* incremental win from doing so seems small.
*/
- if (!RelFileNodeEquals(bufHdr->tag.rnode, rnode.node))
+ if (!RelFileNodeBackendEquals(bufHdr->tag.rnode, rnode))
continue;
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rnode.node) &&
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, rnode) &&
bufHdr->tag.forkNum == forkNum &&
bufHdr->tag.blockNum >= firstDelBlock)
InvalidateBuffer(bufHdr); /* releases spinlock */
@@ -2985,24 +2987,24 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
{
int i,
n = 0;
- RelFileNode *nodes;
+ RelFileNodeBackend *nodes;
bool use_bsearch;
if (nnodes == 0)
return;
- nodes = palloc(sizeof(RelFileNode) * nnodes); /* non-local relations */
+ nodes = palloc(sizeof(RelFileNodeBackend) * nnodes); /* non-local relations */
/* If it's a local relation, it's localbuf.c's problem. */
for (i = 0; i < nnodes; i++)
{
- if (RelFileNodeBackendIsTemp(rnodes[i]))
+ if (RelFileNodeBackendIsLocalTemp(rnodes[i]))
{
if (rnodes[i].backend == MyBackendId)
DropRelFileNodeAllLocalBuffers(rnodes[i].node);
}
else
- nodes[n++] = rnodes[i].node;
+ nodes[n++] = rnodes[i];
}
/*
@@ -3025,11 +3027,11 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
/* sort the list of rnodes if necessary */
if (use_bsearch)
- pg_qsort(nodes, n, sizeof(RelFileNode), rnode_comparator);
+ pg_qsort(nodes, n, sizeof(RelFileNodeBackend), rnode_comparator);
for (i = 0; i < NBuffers; i++)
{
- RelFileNode *rnode = NULL;
+ RelFileNodeBackend *rnode = NULL;
BufferDesc *bufHdr = GetBufferDescriptor(i);
uint32 buf_state;
@@ -3044,7 +3046,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
for (j = 0; j < n; j++)
{
- if (RelFileNodeEquals(bufHdr->tag.rnode, nodes[j]))
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, nodes[j]))
{
rnode = &nodes[j];
break;
@@ -3054,7 +3056,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
else
{
rnode = bsearch((const void *) &(bufHdr->tag.rnode),
- nodes, n, sizeof(RelFileNode),
+ nodes, n, sizeof(RelFileNodeBackend),
rnode_comparator);
}
@@ -3063,7 +3065,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
continue;
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, (*rnode)))
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, (*rnode)))
InvalidateBuffer(bufHdr); /* releases spinlock */
else
UnlockBufHdr(bufHdr, buf_state);
@@ -3102,11 +3104,11 @@ DropDatabaseBuffers(Oid dbid)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (bufHdr->tag.rnode.dbNode != dbid)
+ if (bufHdr->tag.rnode.node.dbNode != dbid)
continue;
buf_state = LockBufHdr(bufHdr);
- if (bufHdr->tag.rnode.dbNode == dbid)
+ if (bufHdr->tag.rnode.node.dbNode == dbid)
InvalidateBuffer(bufHdr); /* releases spinlock */
else
UnlockBufHdr(bufHdr, buf_state);
@@ -3136,7 +3138,7 @@ PrintBufferDescs(void)
"[%02d] (freeNext=%d, rel=%s, "
"blockNum=%u, flags=0x%x, refcount=%u %d)",
i, buf->freeNext,
- relpathbackend(buf->tag.rnode, InvalidBackendId, buf->tag.forkNum),
+ relpath(buf->tag.rnode, buf->tag.forkNum),
buf->tag.blockNum, buf->flags,
buf->refcount, GetPrivateRefCount(b));
}
@@ -3204,7 +3206,8 @@ FlushRelationBuffers(Relation rel)
uint32 buf_state;
bufHdr = GetLocalBufferDescriptor(i);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+ if (RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node) &&
+ bufHdr->tag.rnode.backend == rel->rd_backend &&
((buf_state = pg_atomic_read_u32(&bufHdr->state)) &
(BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
@@ -3251,13 +3254,15 @@ FlushRelationBuffers(Relation rel)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
+ if (!RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node)
+ || bufHdr->tag.rnode.backend != rel->rd_backend)
continue;
ReservePrivateRefCountEntry();
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+ if (RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node) &&
+ bufHdr->tag.rnode.backend == rel->rd_backend &&
(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
PinBuffer_Locked(bufHdr);
@@ -3305,13 +3310,13 @@ FlushDatabaseBuffers(Oid dbid)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (bufHdr->tag.rnode.dbNode != dbid)
+ if (bufHdr->tag.rnode.node.dbNode != dbid)
continue;
ReservePrivateRefCountEntry();
buf_state = LockBufHdr(bufHdr);
- if (bufHdr->tag.rnode.dbNode == dbid &&
+ if (bufHdr->tag.rnode.node.dbNode == dbid &&
(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
PinBuffer_Locked(bufHdr);
@@ -4051,7 +4056,7 @@ AbortBufferIO(void)
/* Buffer is pinned, so we can read tag without spinlock */
char *path;
- path = relpathperm(buf->tag.rnode, buf->tag.forkNum);
+ path = relpath(buf->tag.rnode, buf->tag.forkNum);
ereport(WARNING,
(errcode(ERRCODE_IO_ERROR),
errmsg("could not write block %u of %s",
@@ -4075,7 +4080,7 @@ shared_buffer_write_error_callback(void *arg)
/* Buffer is pinned, so we can read the tag without locking the spinlock */
if (bufHdr != NULL)
{
- char *path = relpathperm(bufHdr->tag.rnode, bufHdr->tag.forkNum);
+ char *path = relpath(bufHdr->tag.rnode, bufHdr->tag.forkNum);
errcontext("writing block %u of relation %s",
bufHdr->tag.blockNum, path);
@@ -4093,7 +4098,7 @@ local_buffer_write_error_callback(void *arg)
if (bufHdr != NULL)
{
- char *path = relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ char *path = relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum);
errcontext("writing block %u of relation %s",
@@ -4108,22 +4113,27 @@ local_buffer_write_error_callback(void *arg)
static int
rnode_comparator(const void *p1, const void *p2)
{
- RelFileNode n1 = *(const RelFileNode *) p1;
- RelFileNode n2 = *(const RelFileNode *) p2;
+ RelFileNodeBackend n1 = *(const RelFileNodeBackend *) p1;
+ RelFileNodeBackend n2 = *(const RelFileNodeBackend *) p2;
- if (n1.relNode < n2.relNode)
+ if (n1.node.relNode < n2.node.relNode)
return -1;
- else if (n1.relNode > n2.relNode)
+ else if (n1.node.relNode > n2.node.relNode)
return 1;
- if (n1.dbNode < n2.dbNode)
+ if (n1.node.dbNode < n2.node.dbNode)
return -1;
- else if (n1.dbNode > n2.dbNode)
+ else if (n1.node.dbNode > n2.node.dbNode)
return 1;
- if (n1.spcNode < n2.spcNode)
+ if (n1.node.spcNode < n2.node.spcNode)
return -1;
- else if (n1.spcNode > n2.spcNode)
+ else if (n1.node.spcNode > n2.node.spcNode)
+ return 1;
+
+ if (n1.backend < n2.backend)
+ return -1;
+ else if (n1.backend > n2.backend)
return 1;
else
return 0;
@@ -4359,7 +4369,7 @@ IssuePendingWritebacks(WritebackContext *context)
next = &context->pending_writebacks[i + ahead + 1];
/* different file, stop */
- if (!RelFileNodeEquals(cur->tag.rnode, next->tag.rnode) ||
+ if (!RelFileNodeBackendEquals(cur->tag.rnode, next->tag.rnode) ||
cur->tag.forkNum != next->tag.forkNum)
break;
@@ -4378,7 +4388,7 @@ IssuePendingWritebacks(WritebackContext *context)
i += ahead;
/* and finally tell the kernel to write the data to storage */
- reln = smgropen(tag.rnode, InvalidBackendId);
+ reln = smgropen(tag.rnode.node, tag.rnode.backend);
smgrwriteback(reln, tag.forkNum, tag.blockNum, nblocks);
}
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index f5f6a29..6bd5ecb 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -68,7 +68,7 @@ LocalPrefetchBuffer(SMgrRelation smgr, ForkNumber forkNum,
BufferTag newTag; /* identity of requested block */
LocalBufferLookupEnt *hresult;
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* Initialize local buffers if first request in this session */
if (LocalBufHash == NULL)
@@ -111,7 +111,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
bool found;
uint32 buf_state;
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* Initialize local buffers if first request in this session */
if (LocalBufHash == NULL)
@@ -209,7 +209,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
Page localpage = (char *) LocalBufHdrGetBlock(bufHdr);
/* Find smgr relation for buffer */
- oreln = smgropen(bufHdr->tag.rnode, MyBackendId);
+ oreln = smgropen(bufHdr->tag.rnode.node, MyBackendId);
PageSetChecksumInplace(localpage, bufHdr->tag.blockNum);
@@ -331,14 +331,14 @@ DropRelFileNodeLocalBuffers(RelFileNode rnode, ForkNumber forkNum,
buf_state = pg_atomic_read_u32(&bufHdr->state);
if ((buf_state & BM_TAG_VALID) &&
- RelFileNodeEquals(bufHdr->tag.rnode, rnode) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, rnode) &&
bufHdr->tag.forkNum == forkNum &&
bufHdr->tag.blockNum >= firstDelBlock)
{
if (LocalRefCount[i] != 0)
elog(ERROR, "block %u of %s is still referenced (local %u)",
bufHdr->tag.blockNum,
- relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum),
LocalRefCount[i]);
/* Remove entry from hashtable */
@@ -377,12 +377,12 @@ DropRelFileNodeAllLocalBuffers(RelFileNode rnode)
buf_state = pg_atomic_read_u32(&bufHdr->state);
if ((buf_state & BM_TAG_VALID) &&
- RelFileNodeEquals(bufHdr->tag.rnode, rnode))
+ RelFileNodeEquals(bufHdr->tag.rnode.node, rnode))
{
if (LocalRefCount[i] != 0)
elog(ERROR, "block %u of %s is still referenced (local %u)",
bufHdr->tag.blockNum,
- relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum),
LocalRefCount[i]);
/* Remove entry from hashtable */
diff --git a/src/backend/storage/freespace/fsmpage.c b/src/backend/storage/freespace/fsmpage.c
index cf7f03f..65eb422 100644
--- a/src/backend/storage/freespace/fsmpage.c
+++ b/src/backend/storage/freespace/fsmpage.c
@@ -268,13 +268,13 @@ restart:
*
* Fix the corruption and restart.
*/
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forknum;
BlockNumber blknum;
BufferGetTag(buf, &rnode, &forknum, &blknum);
elog(DEBUG1, "fixing corrupt FSM block %u, relation %u/%u/%u",
- blknum, rnode.spcNode, rnode.dbNode, rnode.relNode);
+ blknum, rnode.node.spcNode, rnode.node.dbNode, rnode.node.relNode);
/* make sure we hold an exclusive lock */
if (!exclusive_lock_held)
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index fadab62..055ec6b 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -994,6 +994,9 @@ TransactionIdIsInProgress(TransactionId xid)
int i,
j;
+ if (AccessTempRelationAtReplica)
+ return IsReplicaCurrentTransactionId(xid) && !IsReplicaTransactionAborted(xid);
+
/*
* Don't bother checking a transaction older than RecentXmin; it could not
* possibly still be running. (Note: in particular, this guarantees that
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 07f3c93..204c4cb 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,48 @@ 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)
+ {
+ /* Remove relation pages from shared buffers */
+ DropRelFileNodesAllBuffers(&rel->rnode, 1);
+
+ /* 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 +273,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 +522,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 +546,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 +723,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 +814,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 2488607..86e8fca 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 0cc9ede..1dff0c8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15593,8 +15593,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/access/heapam.h b/src/include/access/heapam.h
index 858bcb6..2f16c58 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -195,9 +195,9 @@ extern void heap_vacuum_rel(Relation onerel,
struct VacuumParams *params, BufferAccessStrategy bstrategy);
/* in heap/heapam_visibility.c */
-extern bool HeapTupleSatisfiesVisibility(HeapTuple stup, Snapshot snapshot,
+extern bool HeapTupleSatisfiesVisibility(Relation relation, HeapTuple stup, Snapshot snapshot,
Buffer buffer);
-extern TM_Result HeapTupleSatisfiesUpdate(HeapTuple stup, CommandId curcid,
+extern TM_Result HeapTupleSatisfiesUpdate(Relation relation, HeapTuple stup, CommandId curcid,
Buffer buffer);
extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTuple stup, TransactionId OldestXmin,
Buffer buffer);
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index d714551..cbe6760 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -41,6 +41,9 @@
extern int DefaultXactIsoLevel;
extern PGDLLIMPORT int XactIsoLevel;
+extern bool AccessTempRelationAtReplica;
+
+
/*
* We implement three isolation levels internally.
* The two stronger ones use one snapshot per database transaction;
@@ -440,4 +443,8 @@ extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+extern TransactionId GetReplicaTransactionId(void);
+extern bool IsReplicaTransactionAborted(TransactionId xid);
+extern bool IsReplicaCurrentTransactionId(TransactionId xid);
+
#endif /* XACT_H */
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/buf_internals.h b/src/include/storage/buf_internals.h
index df2dda7..7adb96b 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -90,16 +90,17 @@
*/
typedef struct buftag
{
- RelFileNode rnode; /* physical relation identifier */
+ RelFileNodeBackend rnode; /* physical relation identifier */
ForkNumber forkNum;
BlockNumber blockNum; /* blknum relative to begin of reln */
} BufferTag;
#define CLEAR_BUFFERTAG(a) \
( \
- (a).rnode.spcNode = InvalidOid, \
- (a).rnode.dbNode = InvalidOid, \
- (a).rnode.relNode = InvalidOid, \
+ (a).rnode.node.spcNode = InvalidOid, \
+ (a).rnode.node.dbNode = InvalidOid, \
+ (a).rnode.node.relNode = InvalidOid, \
+ (a).rnode.backend = InvalidBackendId, \
(a).forkNum = InvalidForkNumber, \
(a).blockNum = InvalidBlockNumber \
)
@@ -113,7 +114,7 @@ typedef struct buftag
#define BUFFERTAGS_EQUAL(a,b) \
( \
- RelFileNodeEquals((a).rnode, (b).rnode) && \
+ RelFileNodeBackendEquals((a).rnode, (b).rnode) && \
(a).blockNum == (b).blockNum && \
(a).forkNum == (b).forkNum \
)
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 509f4b7..3315fa0 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -205,7 +205,7 @@ extern XLogRecPtr BufferGetLSNAtomic(Buffer buffer);
extern void PrintPinnedBufs(void);
#endif
extern Size BufferShmemSize(void);
-extern void BufferGetTag(Buffer buffer, RelFileNode *rnode,
+extern void BufferGetTag(Buffer buffer, RelFileNodeBackend *rnode,
ForkNumber *forknum, BlockNumber *blknum);
extern void MarkBufferDirtyHint(Buffer buffer, bool buffer_std);
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 b0fe19e..b361851 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -328,6 +328,17 @@ 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
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 74b5077..44df4e0 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -85,3 +85,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..b7bf067
--- /dev/null
+++ b/src/test/regress/expected/global_temp.out
@@ -0,0 +1,323 @@
+--
+-- 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)
+
+DROP TABLE global_temptest2;
+DROP TABLE global_temptest1;
+-- Unsupported ON COMMIT and foreign key combination
+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;
+-- Test two phase commit
+CREATE TABLE global_temptest_persistent(col int);
+CREATE GLOBAL TEMP TABLE global_temptest(col int);
+INSERT INTO global_temptest VALUES (1);
+BEGIN;
+INSERT INTO global_temptest VALUES (2);
+SELECT * FROM global_temptest;
+ col
+-----
+ 1
+ 2
+(2 rows)
+
+PREPARE TRANSACTION 'global_temp1';
+-- We can't see anything from an uncommitted transaction
+SELECT * FROM global_temptest;
+ col
+-----
+ 1
+(1 row)
+
+BEGIN;
+INSERT INTO global_temptest VALUES (3);
+INSERT INTO global_temptest_persistent SELECT * FROM global_temptest;
+PREPARE TRANSACTION 'global_temp2';
+COMMIT PREPARED 'global_temp1';
+-- 1, 2
+SELECT * FROM global_temptest;
+ col
+-----
+ 1
+ 2
+(2 rows)
+
+-- Nothing
+SELECT * FROM global_temptest_persistent;
+ col
+-----
+(0 rows)
+
+\c
+-- The temp table is empty now.
+SELECT * FROM global_temptest;
+ col
+-----
+(0 rows)
+
+-- Still nothing in global_temptest_persistent table;
+SELECT * FROM global_temptest_persistent;
+ col
+-----
+(0 rows)
+
+INSERT INTO global_temptest VALUES (4);
+COMMIT PREPARED 'global_temp2';
+-- Only 4
+SELECT * FROM global_temptest;
+ col
+-----
+ 4
+(1 row)
+
+-- 1, 3
+SELECT * FROM global_temptest_persistent;
+ col
+-----
+ 1
+ 3
+(2 rows)
+
+\c
+DROP TABLE global_temptest;
+DROP TABLE global_temptest_persistent;
diff --git a/src/test/regress/expected/global_temp_0.out b/src/test/regress/expected/global_temp_0.out
new file mode 100644
index 0000000..934e751
--- /dev/null
+++ b/src/test/regress/expected/global_temp_0.out
@@ -0,0 +1,326 @@
+--
+-- 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)
+
+DROP TABLE global_temptest2;
+DROP TABLE global_temptest1;
+-- Unsupported ON COMMIT and foreign key combination
+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;
+-- Test two phase commit
+CREATE TABLE global_temptest_persistent(col int);
+CREATE GLOBAL TEMP TABLE global_temptest(col int);
+INSERT INTO global_temptest VALUES (1);
+BEGIN;
+INSERT INTO global_temptest VALUES (2);
+SELECT * FROM global_temptest;
+ col
+-----
+ 1
+ 2
+(2 rows)
+
+PREPARE TRANSACTION 'global_temp1';
+ERROR: prepared transactions are disabled
+HINT: Set max_prepared_transactions to a nonzero value.
+-- We can't see anything from an uncommitted transaction
+SELECT * FROM global_temptest;
+ col
+-----
+ 1
+(1 row)
+
+BEGIN;
+INSERT INTO global_temptest VALUES (3);
+INSERT INTO global_temptest_persistent SELECT * FROM global_temptest;
+PREPARE TRANSACTION 'global_temp2';
+ERROR: prepared transactions are disabled
+HINT: Set max_prepared_transactions to a nonzero value.
+COMMIT PREPARED 'global_temp1';
+ERROR: prepared transaction with identifier "global_temp1" does not exist
+-- 1, 2
+SELECT * FROM global_temptest;
+ col
+-----
+ 1
+(1 row)
+
+-- Nothing
+SELECT * FROM global_temptest_persistent;
+ col
+-----
+(0 rows)
+
+\c
+-- The temp table is empty now.
+SELECT * FROM global_temptest;
+ col
+-----
+(0 rows)
+
+-- Still nothing in global_temptest_persistent table;
+SELECT * FROM global_temptest_persistent;
+ col
+-----
+(0 rows)
+
+INSERT INTO global_temptest VALUES (4);
+COMMIT PREPARED 'global_temp2';
+ERROR: prepared transaction with identifier "global_temp2" does not exist
+-- Only 4
+SELECT * FROM global_temptest;
+ col
+-----
+ 4
+(1 row)
+
+-- 1, 3
+SELECT * FROM global_temptest_persistent;
+ col
+-----
+(0 rows)
+
+\c
+DROP TABLE global_temptest;
+DROP TABLE global_temptest_persistent;
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..4d2da8d
--- /dev/null
+++ b/src/test/regress/sql/global_temp.sql
@@ -0,0 +1,191 @@
+--
+-- 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;
+
+DROP TABLE global_temptest2;
+DROP TABLE global_temptest1;
+
+-- Unsupported ON COMMIT and foreign key combination
+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;
+
+-- Test two phase commit
+CREATE TABLE global_temptest_persistent(col int);
+CREATE GLOBAL TEMP TABLE global_temptest(col int);
+INSERT INTO global_temptest VALUES (1);
+
+BEGIN;
+INSERT INTO global_temptest VALUES (2);
+SELECT * FROM global_temptest;
+PREPARE TRANSACTION 'global_temp1';
+-- We can't see anything from an uncommitted transaction
+SELECT * FROM global_temptest;
+
+BEGIN;
+INSERT INTO global_temptest VALUES (3);
+INSERT INTO global_temptest_persistent SELECT * FROM global_temptest;
+PREPARE TRANSACTION 'global_temp2';
+COMMIT PREPARED 'global_temp1';
+-- 1, 2
+SELECT * FROM global_temptest;
+-- Nothing
+SELECT * FROM global_temptest_persistent;
+\c
+-- The temp table is empty now.
+SELECT * FROM global_temptest;
+-- Still nothing in global_temptest_persistent table;
+SELECT * FROM global_temptest_persistent;
+INSERT INTO global_temptest VALUES (4);
+COMMIT PREPARED 'global_temp2';
+-- Only 4
+SELECT * FROM global_temptest;
+-- 1, 3
+SELECT * FROM global_temptest_persistent;
+\c
+DROP TABLE global_temptest;
+DROP TABLE global_temptest_persistent;
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;
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-09-20 15:12 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
1 sibling, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-09-20 15:12 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
I have added support of all indexes (brin, btree, gin, gist, hash,
spgist) for global temp tables (before only B-Tree index was supported).
It will be nice to have some generic mechanism for it, but I do not
understand how it can look like.
The problem is that normal relations are initialized at the moment of
their creation.
But for global temp relations metadata already exists while data is
absent. We should somehow catch such access to not initialized page (but
not not all pages, but just first page of relation)
and perform initialization on demand.
New patch for global temp tables with shared buffers is attached.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
[text/x-patch] global_shared_temp_replica-2.patch (114.6K, ../../ce403182-f52e-5a91-ed29-7404f7a67797@postgrespro.ru/2-global_shared_temp_replica-2.patch)
download | inline diff:
diff --git a/contrib/pg_buffercache/pg_buffercache_pages.c b/contrib/pg_buffercache/pg_buffercache_pages.c
index 1bd579f..2d93f6f 100644
--- a/contrib/pg_buffercache/pg_buffercache_pages.c
+++ b/contrib/pg_buffercache/pg_buffercache_pages.c
@@ -153,9 +153,9 @@ pg_buffercache_pages(PG_FUNCTION_ARGS)
buf_state = LockBufHdr(bufHdr);
fctx->record[i].bufferid = BufferDescriptorGetBuffer(bufHdr);
- fctx->record[i].relfilenode = bufHdr->tag.rnode.relNode;
- fctx->record[i].reltablespace = bufHdr->tag.rnode.spcNode;
- fctx->record[i].reldatabase = bufHdr->tag.rnode.dbNode;
+ fctx->record[i].relfilenode = bufHdr->tag.rnode.node.relNode;
+ fctx->record[i].reltablespace = bufHdr->tag.rnode.node.spcNode;
+ fctx->record[i].reldatabase = bufHdr->tag.rnode.node.dbNode;
fctx->record[i].forknum = bufHdr->tag.forkNum;
fctx->record[i].blocknum = bufHdr->tag.blockNum;
fctx->record[i].usagecount = BUF_STATE_GET_USAGECOUNT(buf_state);
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 38ae240..8a04954 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -608,9 +608,9 @@ apw_dump_now(bool is_bgworker, bool dump_unlogged)
if (buf_state & BM_TAG_VALID &&
((buf_state & BM_PERMANENT) || dump_unlogged))
{
- block_info_array[num_blocks].database = bufHdr->tag.rnode.dbNode;
- block_info_array[num_blocks].tablespace = bufHdr->tag.rnode.spcNode;
- block_info_array[num_blocks].filenode = bufHdr->tag.rnode.relNode;
+ block_info_array[num_blocks].database = bufHdr->tag.rnode.node.dbNode;
+ block_info_array[num_blocks].tablespace = bufHdr->tag.rnode.node.spcNode;
+ block_info_array[num_blocks].filenode = bufHdr->tag.rnode.node.relNode;
block_info_array[num_blocks].forknum = bufHdr->tag.forkNum;
block_info_array[num_blocks].blocknum = bufHdr->tag.blockNum;
++num_blocks;
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index a2c44a9..43b4c66 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -158,7 +158,8 @@ pgrowlocks(PG_FUNCTION_ARGS)
/* must hold a buffer lock to call HeapTupleSatisfiesUpdate */
LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
- htsu = HeapTupleSatisfiesUpdate(tuple,
+ htsu = HeapTupleSatisfiesUpdate(mydata->rel,
+ tuple,
GetCurrentCommandId(false),
hscan->rs_cbuf);
xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 70af43e..9cce720 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -349,7 +349,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
/* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
- if (HeapTupleSatisfiesVisibility(tuple, &SnapshotDirty, hscan->rs_cbuf))
+ if (HeapTupleSatisfiesVisibility(rel, tuple, &SnapshotDirty, hscan->rs_cbuf))
{
stat.tuple_len += tuple->t_len;
stat.tuple_count++;
diff --git a/src/backend/access/brin/brin_revmap.c b/src/backend/access/brin/brin_revmap.c
index e2bfbf8..97041a8 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/gin/ginxlog.c b/src/backend/access/gin/ginxlog.c
index c945b28..14d4e48 100644
--- a/src/backend/access/gin/ginxlog.c
+++ b/src/backend/access/gin/ginxlog.c
@@ -95,13 +95,13 @@ ginRedoInsertEntry(Buffer buffer, bool isLeaf, BlockNumber rightblkno, void *rda
if (PageAddItem(page, (Item) itup, IndexTupleSize(itup), offset, false, false) == InvalidOffsetNumber)
{
- RelFileNode node;
+ RelFileNodeBackend rnode;
ForkNumber forknum;
BlockNumber blknum;
- BufferGetTag(buffer, &node, &forknum, &blknum);
+ BufferGetTag(buffer, &rnode, &forknum, &blknum);
elog(ERROR, "failed to add item to index page in %u/%u/%u",
- node.spcNode, node.dbNode, node.relNode);
+ rnode.node.spcNode, rnode.node.dbNode, rnode.node.relNode);
}
}
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index e9ca4b8..d3ea072 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 4e0c500..cced239 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -339,7 +339,10 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
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 9726020..c99701d 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 defdc9b..35de5fa 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.c b/src/backend/access/heap/heapam.c
index 9430994..181efde 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);
@@ -1612,7 +1613,7 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
ItemPointerSet(&(heapTuple->t_self), BufferGetBlockNumber(buffer), offnum);
/* 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);
/* reset to original, non-redirected, tid */
@@ -1754,7 +1755,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;
@@ -1851,6 +1852,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
*
@@ -1873,7 +1882,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;
@@ -2110,7 +2119,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;
@@ -2449,7 +2458,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;
@@ -2514,7 +2523,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)
{
@@ -2633,7 +2642,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;
}
@@ -2900,7 +2909,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;
@@ -3070,7 +3079,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);
@@ -3262,7 +3271,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));
@@ -4018,7 +4027,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)
{
@@ -4193,7 +4202,7 @@ l3:
TM_Result res;
res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
- GetCurrentTransactionId(),
+ GetTransactionId(relation),
mode);
if (res != TM_Ok)
{
@@ -4441,7 +4450,7 @@ l3:
TM_Result res;
res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
- GetCurrentTransactionId(),
+ GetTransactionId(relation),
mode);
if (res != TM_Ok)
{
@@ -4550,7 +4559,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();
@@ -5570,7 +5579,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 09bc6fe..a189834 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);
@@ -671,6 +672,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);
@@ -2160,7 +2162,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;
@@ -2480,7 +2482,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/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 5962126..bdb6c95 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/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/varsup.c b/src/backend/access/transam/varsup.c
index 5b759ec..388faae 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -71,7 +71,7 @@ GetNewTransactionId(bool isSubXact)
/* safety check, we should never get this far in a HS standby */
if (RecoveryInProgress())
- elog(ERROR, "cannot assign TransactionIds during recovery");
+ elog(ERROR, "cannot assign TransactionIds during recovery");
LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 1bbaeee..ab1bef9 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;
@@ -4856,6 +4922,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();
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index 3ec67d4..edec8ca 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -213,6 +213,7 @@ void
XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
{
registered_buffer *regbuf;
+ RelFileNodeBackend rnode;
/* NO_IMAGE doesn't make sense with FORCE_IMAGE */
Assert(!((flags & REGBUF_FORCE_IMAGE) && (flags & (REGBUF_NO_IMAGE))));
@@ -227,7 +228,8 @@ XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
regbuf = ®istered_buffers[block_id];
- BufferGetTag(buffer, ®buf->rnode, ®buf->forkno, ®buf->block);
+ BufferGetTag(buffer, &rnode, ®buf->forkno, ®buf->block);
+ regbuf->rnode = rnode.node;
regbuf->page = BufferGetPage(buffer);
regbuf->flags = flags;
regbuf->rdata_tail = (XLogRecData *) ®buf->rdata_head;
@@ -919,7 +921,7 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std)
int flags;
PGAlignedBlock copied_buffer;
char *origdata = (char *) BufferGetBlock(buffer);
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forkno;
BlockNumber blkno;
@@ -948,7 +950,7 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std)
flags |= REGBUF_STANDARD;
BufferGetTag(buffer, &rnode, &forkno, &blkno);
- XLogRegisterBlock(0, &rnode, forkno, blkno, copied_buffer.data, flags);
+ XLogRegisterBlock(0, &rnode.node, forkno, blkno, copied_buffer.data, flags);
recptr = XLogInsert(RM_XLOG_ID, XLOG_FPI_FOR_HINT);
}
@@ -1009,7 +1011,7 @@ XLogRecPtr
log_newpage_buffer(Buffer buffer, bool page_std)
{
Page page = BufferGetPage(buffer);
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forkNum;
BlockNumber blkno;
@@ -1018,7 +1020,7 @@ log_newpage_buffer(Buffer buffer, bool page_std)
BufferGetTag(buffer, &rnode, &forkNum, &blkno);
- return log_newpage(&rnode, forkNum, blkno, page, page_std);
+ return log_newpage(&rnode.node, forkNum, blkno, page, page_std);
}
/*
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index a065419..8814afb 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -409,6 +409,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 99ae159..24b2438 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3612,7 +3612,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 3cc886f..a111ddc 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 cedb4ee..d11c5b3 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 0960b33..6c3998f 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 fb2be10..a31f775 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -586,7 +586,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);
@@ -7678,6 +7679,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,
@@ -14082,6 +14089,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 */
@@ -14569,14 +14583,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/executor/execMain.c b/src/backend/executor/execMain.c
index dbd7dd9..efe6f21 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -788,6 +788,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
if (isTempNamespace(get_rel_namespace(rte->relid)))
continue;
+ if (get_rel_persistence(rte->relid) == RELPERSISTENCE_SESSION)
+ continue;
+
PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 98e9948..1a9170b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -124,7 +124,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
relation = table_open(relationObjectId, NoLock);
/* Temporary and unlogged relations are inaccessible during recovery. */
- if (!RelationNeedsWAL(relation) && RecoveryInProgress())
+ if (!RelationNeedsWAL(relation) && RecoveryInProgress() && relation->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot access temporary or unlogged relations during recovery")));
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c97bb36..f9b2000 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3265,20 +3265,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 a9b2f8b..2f261b9 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 073f313..5760a9c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2154,7 +2154,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/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index e8ffa04..2004d2f 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -3483,6 +3483,7 @@ ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
{
ReorderBufferTupleCidKey key;
ReorderBufferTupleCidEnt *ent;
+ RelFileNodeBackend rnode;
ForkNumber forkno;
BlockNumber blockno;
bool updated_mapping = false;
@@ -3496,7 +3497,8 @@ ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
* get relfilenode from the buffer, no convenient way to access it other
* than that.
*/
- BufferGetTag(buffer, &key.relnode, &forkno, &blockno);
+ BufferGetTag(buffer, &rnode, &forkno, &blockno);
+ key.relnode = rnode.node;
/* tuples can only be in the main fork */
Assert(forkno == MAIN_FORKNUM);
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 6f3a402..76ce953 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -556,7 +556,7 @@ PrefetchBuffer(Relation reln, ForkNumber forkNum, BlockNumber blockNum)
int buf_id;
/* create a tag so we can lookup the buffer */
- INIT_BUFFERTAG(newTag, reln->rd_smgr->smgr_rnode.node,
+ INIT_BUFFERTAG(newTag, reln->rd_smgr->smgr_rnode,
forkNum, blockNum);
/* determine its hash code and partition lock ID */
@@ -710,7 +710,7 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
Block bufBlock;
bool found;
bool isExtend;
- bool isLocalBuf = SmgrIsTemp(smgr);
+ bool isLocalBuf = SmgrIsTemp(smgr) && relpersistence == RELPERSISTENCE_TEMP;
*hit = false;
@@ -1010,7 +1010,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
uint32 buf_state;
/* create a tag so we can lookup the buffer */
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* determine its hash code and partition lock ID */
newHash = BufTableHashCode(&newTag);
@@ -1532,7 +1532,8 @@ ReleaseAndReadBuffer(Buffer buffer,
{
bufHdr = GetLocalBufferDescriptor(-buffer - 1);
if (bufHdr->tag.blockNum == blockNum &&
- RelFileNodeEquals(bufHdr->tag.rnode, relation->rd_node) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, relation->rd_node) &&
+ bufHdr->tag.rnode.backend == relation->rd_backend &&
bufHdr->tag.forkNum == forkNum)
return buffer;
ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
@@ -1543,7 +1544,8 @@ ReleaseAndReadBuffer(Buffer buffer,
bufHdr = GetBufferDescriptor(buffer - 1);
/* we have pin, so it's ok to examine tag without spinlock */
if (bufHdr->tag.blockNum == blockNum &&
- RelFileNodeEquals(bufHdr->tag.rnode, relation->rd_node) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, relation->rd_node) &&
+ bufHdr->tag.rnode.backend == relation->rd_backend &&
bufHdr->tag.forkNum == forkNum)
return buffer;
UnpinBuffer(bufHdr, true);
@@ -1845,8 +1847,8 @@ BufferSync(int flags)
item = &CkptBufferIds[num_to_scan++];
item->buf_id = buf_id;
- item->tsId = bufHdr->tag.rnode.spcNode;
- item->relNode = bufHdr->tag.rnode.relNode;
+ item->tsId = bufHdr->tag.rnode.node.spcNode;
+ item->relNode = bufHdr->tag.rnode.node.relNode;
item->forkNum = bufHdr->tag.forkNum;
item->blockNum = bufHdr->tag.blockNum;
}
@@ -2559,7 +2561,7 @@ PrintBufferLeakWarning(Buffer buffer)
}
/* theoretically we should lock the bufhdr here */
- path = relpathbackend(buf->tag.rnode, backend, buf->tag.forkNum);
+ path = relpathbackend(buf->tag.rnode.node, backend, buf->tag.forkNum);
buf_state = pg_atomic_read_u32(&buf->state);
elog(WARNING,
"buffer refcount leak: [%03d] "
@@ -2631,7 +2633,7 @@ BufferGetBlockNumber(Buffer buffer)
* a buffer.
*/
void
-BufferGetTag(Buffer buffer, RelFileNode *rnode, ForkNumber *forknum,
+BufferGetTag(Buffer buffer, RelFileNodeBackend *rnode, ForkNumber *forknum,
BlockNumber *blknum)
{
BufferDesc *bufHdr;
@@ -2696,7 +2698,7 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
/* Find smgr relation for buffer */
if (reln == NULL)
- reln = smgropen(buf->tag.rnode, InvalidBackendId);
+ reln = smgropen(buf->tag.rnode.node, buf->tag.rnode.backend);
TRACE_POSTGRESQL_BUFFER_FLUSH_START(buf->tag.forkNum,
buf->tag.blockNum,
@@ -2930,7 +2932,7 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber forkNum,
int i;
/* If it's a local relation, it's localbuf.c's problem. */
- if (RelFileNodeBackendIsTemp(rnode))
+ if (RelFileNodeBackendIsLocalTemp(rnode))
{
if (rnode.backend == MyBackendId)
DropRelFileNodeLocalBuffers(rnode.node, forkNum, firstDelBlock);
@@ -2958,11 +2960,11 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber forkNum,
* We could check forkNum and blockNum as well as the rnode, but the
* incremental win from doing so seems small.
*/
- if (!RelFileNodeEquals(bufHdr->tag.rnode, rnode.node))
+ if (!RelFileNodeBackendEquals(bufHdr->tag.rnode, rnode))
continue;
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rnode.node) &&
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, rnode) &&
bufHdr->tag.forkNum == forkNum &&
bufHdr->tag.blockNum >= firstDelBlock)
InvalidateBuffer(bufHdr); /* releases spinlock */
@@ -2985,24 +2987,24 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
{
int i,
n = 0;
- RelFileNode *nodes;
+ RelFileNodeBackend *nodes;
bool use_bsearch;
if (nnodes == 0)
return;
- nodes = palloc(sizeof(RelFileNode) * nnodes); /* non-local relations */
+ nodes = palloc(sizeof(RelFileNodeBackend) * nnodes); /* non-local relations */
/* If it's a local relation, it's localbuf.c's problem. */
for (i = 0; i < nnodes; i++)
{
- if (RelFileNodeBackendIsTemp(rnodes[i]))
+ if (RelFileNodeBackendIsLocalTemp(rnodes[i]))
{
if (rnodes[i].backend == MyBackendId)
DropRelFileNodeAllLocalBuffers(rnodes[i].node);
}
else
- nodes[n++] = rnodes[i].node;
+ nodes[n++] = rnodes[i];
}
/*
@@ -3025,11 +3027,11 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
/* sort the list of rnodes if necessary */
if (use_bsearch)
- pg_qsort(nodes, n, sizeof(RelFileNode), rnode_comparator);
+ pg_qsort(nodes, n, sizeof(RelFileNodeBackend), rnode_comparator);
for (i = 0; i < NBuffers; i++)
{
- RelFileNode *rnode = NULL;
+ RelFileNodeBackend *rnode = NULL;
BufferDesc *bufHdr = GetBufferDescriptor(i);
uint32 buf_state;
@@ -3044,7 +3046,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
for (j = 0; j < n; j++)
{
- if (RelFileNodeEquals(bufHdr->tag.rnode, nodes[j]))
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, nodes[j]))
{
rnode = &nodes[j];
break;
@@ -3054,7 +3056,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
else
{
rnode = bsearch((const void *) &(bufHdr->tag.rnode),
- nodes, n, sizeof(RelFileNode),
+ nodes, n, sizeof(RelFileNodeBackend),
rnode_comparator);
}
@@ -3063,7 +3065,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
continue;
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, (*rnode)))
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, (*rnode)))
InvalidateBuffer(bufHdr); /* releases spinlock */
else
UnlockBufHdr(bufHdr, buf_state);
@@ -3102,11 +3104,11 @@ DropDatabaseBuffers(Oid dbid)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (bufHdr->tag.rnode.dbNode != dbid)
+ if (bufHdr->tag.rnode.node.dbNode != dbid)
continue;
buf_state = LockBufHdr(bufHdr);
- if (bufHdr->tag.rnode.dbNode == dbid)
+ if (bufHdr->tag.rnode.node.dbNode == dbid)
InvalidateBuffer(bufHdr); /* releases spinlock */
else
UnlockBufHdr(bufHdr, buf_state);
@@ -3136,7 +3138,7 @@ PrintBufferDescs(void)
"[%02d] (freeNext=%d, rel=%s, "
"blockNum=%u, flags=0x%x, refcount=%u %d)",
i, buf->freeNext,
- relpathbackend(buf->tag.rnode, InvalidBackendId, buf->tag.forkNum),
+ relpath(buf->tag.rnode, buf->tag.forkNum),
buf->tag.blockNum, buf->flags,
buf->refcount, GetPrivateRefCount(b));
}
@@ -3204,7 +3206,8 @@ FlushRelationBuffers(Relation rel)
uint32 buf_state;
bufHdr = GetLocalBufferDescriptor(i);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+ if (RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node) &&
+ bufHdr->tag.rnode.backend == rel->rd_backend &&
((buf_state = pg_atomic_read_u32(&bufHdr->state)) &
(BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
@@ -3251,13 +3254,15 @@ FlushRelationBuffers(Relation rel)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
+ if (!RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node)
+ || bufHdr->tag.rnode.backend != rel->rd_backend)
continue;
ReservePrivateRefCountEntry();
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+ if (RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node) &&
+ bufHdr->tag.rnode.backend == rel->rd_backend &&
(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
PinBuffer_Locked(bufHdr);
@@ -3305,13 +3310,13 @@ FlushDatabaseBuffers(Oid dbid)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (bufHdr->tag.rnode.dbNode != dbid)
+ if (bufHdr->tag.rnode.node.dbNode != dbid)
continue;
ReservePrivateRefCountEntry();
buf_state = LockBufHdr(bufHdr);
- if (bufHdr->tag.rnode.dbNode == dbid &&
+ if (bufHdr->tag.rnode.node.dbNode == dbid &&
(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
PinBuffer_Locked(bufHdr);
@@ -4051,7 +4056,7 @@ AbortBufferIO(void)
/* Buffer is pinned, so we can read tag without spinlock */
char *path;
- path = relpathperm(buf->tag.rnode, buf->tag.forkNum);
+ path = relpath(buf->tag.rnode, buf->tag.forkNum);
ereport(WARNING,
(errcode(ERRCODE_IO_ERROR),
errmsg("could not write block %u of %s",
@@ -4075,7 +4080,7 @@ shared_buffer_write_error_callback(void *arg)
/* Buffer is pinned, so we can read the tag without locking the spinlock */
if (bufHdr != NULL)
{
- char *path = relpathperm(bufHdr->tag.rnode, bufHdr->tag.forkNum);
+ char *path = relpath(bufHdr->tag.rnode, bufHdr->tag.forkNum);
errcontext("writing block %u of relation %s",
bufHdr->tag.blockNum, path);
@@ -4093,7 +4098,7 @@ local_buffer_write_error_callback(void *arg)
if (bufHdr != NULL)
{
- char *path = relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ char *path = relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum);
errcontext("writing block %u of relation %s",
@@ -4108,22 +4113,27 @@ local_buffer_write_error_callback(void *arg)
static int
rnode_comparator(const void *p1, const void *p2)
{
- RelFileNode n1 = *(const RelFileNode *) p1;
- RelFileNode n2 = *(const RelFileNode *) p2;
+ RelFileNodeBackend n1 = *(const RelFileNodeBackend *) p1;
+ RelFileNodeBackend n2 = *(const RelFileNodeBackend *) p2;
- if (n1.relNode < n2.relNode)
+ if (n1.node.relNode < n2.node.relNode)
return -1;
- else if (n1.relNode > n2.relNode)
+ else if (n1.node.relNode > n2.node.relNode)
return 1;
- if (n1.dbNode < n2.dbNode)
+ if (n1.node.dbNode < n2.node.dbNode)
return -1;
- else if (n1.dbNode > n2.dbNode)
+ else if (n1.node.dbNode > n2.node.dbNode)
return 1;
- if (n1.spcNode < n2.spcNode)
+ if (n1.node.spcNode < n2.node.spcNode)
return -1;
- else if (n1.spcNode > n2.spcNode)
+ else if (n1.node.spcNode > n2.node.spcNode)
+ return 1;
+
+ if (n1.backend < n2.backend)
+ return -1;
+ else if (n1.backend > n2.backend)
return 1;
else
return 0;
@@ -4359,7 +4369,7 @@ IssuePendingWritebacks(WritebackContext *context)
next = &context->pending_writebacks[i + ahead + 1];
/* different file, stop */
- if (!RelFileNodeEquals(cur->tag.rnode, next->tag.rnode) ||
+ if (!RelFileNodeBackendEquals(cur->tag.rnode, next->tag.rnode) ||
cur->tag.forkNum != next->tag.forkNum)
break;
@@ -4378,7 +4388,7 @@ IssuePendingWritebacks(WritebackContext *context)
i += ahead;
/* and finally tell the kernel to write the data to storage */
- reln = smgropen(tag.rnode, InvalidBackendId);
+ reln = smgropen(tag.rnode.node, tag.rnode.backend);
smgrwriteback(reln, tag.forkNum, tag.blockNum, nblocks);
}
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index f5f6a29..6bd5ecb 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -68,7 +68,7 @@ LocalPrefetchBuffer(SMgrRelation smgr, ForkNumber forkNum,
BufferTag newTag; /* identity of requested block */
LocalBufferLookupEnt *hresult;
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* Initialize local buffers if first request in this session */
if (LocalBufHash == NULL)
@@ -111,7 +111,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
bool found;
uint32 buf_state;
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* Initialize local buffers if first request in this session */
if (LocalBufHash == NULL)
@@ -209,7 +209,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
Page localpage = (char *) LocalBufHdrGetBlock(bufHdr);
/* Find smgr relation for buffer */
- oreln = smgropen(bufHdr->tag.rnode, MyBackendId);
+ oreln = smgropen(bufHdr->tag.rnode.node, MyBackendId);
PageSetChecksumInplace(localpage, bufHdr->tag.blockNum);
@@ -331,14 +331,14 @@ DropRelFileNodeLocalBuffers(RelFileNode rnode, ForkNumber forkNum,
buf_state = pg_atomic_read_u32(&bufHdr->state);
if ((buf_state & BM_TAG_VALID) &&
- RelFileNodeEquals(bufHdr->tag.rnode, rnode) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, rnode) &&
bufHdr->tag.forkNum == forkNum &&
bufHdr->tag.blockNum >= firstDelBlock)
{
if (LocalRefCount[i] != 0)
elog(ERROR, "block %u of %s is still referenced (local %u)",
bufHdr->tag.blockNum,
- relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum),
LocalRefCount[i]);
/* Remove entry from hashtable */
@@ -377,12 +377,12 @@ DropRelFileNodeAllLocalBuffers(RelFileNode rnode)
buf_state = pg_atomic_read_u32(&bufHdr->state);
if ((buf_state & BM_TAG_VALID) &&
- RelFileNodeEquals(bufHdr->tag.rnode, rnode))
+ RelFileNodeEquals(bufHdr->tag.rnode.node, rnode))
{
if (LocalRefCount[i] != 0)
elog(ERROR, "block %u of %s is still referenced (local %u)",
bufHdr->tag.blockNum,
- relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum),
LocalRefCount[i]);
/* Remove entry from hashtable */
diff --git a/src/backend/storage/freespace/fsmpage.c b/src/backend/storage/freespace/fsmpage.c
index cf7f03f..65eb422 100644
--- a/src/backend/storage/freespace/fsmpage.c
+++ b/src/backend/storage/freespace/fsmpage.c
@@ -268,13 +268,13 @@ restart:
*
* Fix the corruption and restart.
*/
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forknum;
BlockNumber blknum;
BufferGetTag(buf, &rnode, &forknum, &blknum);
elog(DEBUG1, "fixing corrupt FSM block %u, relation %u/%u/%u",
- blknum, rnode.spcNode, rnode.dbNode, rnode.relNode);
+ blknum, rnode.node.spcNode, rnode.node.dbNode, rnode.node.relNode);
/* make sure we hold an exclusive lock */
if (!exclusive_lock_held)
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index fadab62..055ec6b 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -994,6 +994,9 @@ TransactionIdIsInProgress(TransactionId xid)
int i,
j;
+ if (AccessTempRelationAtReplica)
+ return IsReplicaCurrentTransactionId(xid) && !IsReplicaTransactionAborted(xid);
+
/*
* Don't bother checking a transaction older than RecentXmin; it could not
* possibly still be running. (Note: in particular, this guarantees that
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 07f3c93..204c4cb 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,48 @@ 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)
+ {
+ /* Remove relation pages from shared buffers */
+ DropRelFileNodesAllBuffers(&rel->rnode, 1);
+
+ /* 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 +273,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 +522,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 +546,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 +723,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 +814,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 2488607..86e8fca 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 0cc9ede..1dff0c8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15593,8 +15593,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/access/heapam.h b/src/include/access/heapam.h
index 858bcb6..2f16c58 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -195,9 +195,9 @@ extern void heap_vacuum_rel(Relation onerel,
struct VacuumParams *params, BufferAccessStrategy bstrategy);
/* in heap/heapam_visibility.c */
-extern bool HeapTupleSatisfiesVisibility(HeapTuple stup, Snapshot snapshot,
+extern bool HeapTupleSatisfiesVisibility(Relation relation, HeapTuple stup, Snapshot snapshot,
Buffer buffer);
-extern TM_Result HeapTupleSatisfiesUpdate(HeapTuple stup, CommandId curcid,
+extern TM_Result HeapTupleSatisfiesUpdate(Relation relation, HeapTuple stup, CommandId curcid,
Buffer buffer);
extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTuple stup, TransactionId OldestXmin,
Buffer buffer);
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index d714551..cbe6760 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -41,6 +41,9 @@
extern int DefaultXactIsoLevel;
extern PGDLLIMPORT int XactIsoLevel;
+extern bool AccessTempRelationAtReplica;
+
+
/*
* We implement three isolation levels internally.
* The two stronger ones use one snapshot per database transaction;
@@ -440,4 +443,8 @@ extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+extern TransactionId GetReplicaTransactionId(void);
+extern bool IsReplicaTransactionAborted(TransactionId xid);
+extern bool IsReplicaCurrentTransactionId(TransactionId xid);
+
#endif /* XACT_H */
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/buf_internals.h b/src/include/storage/buf_internals.h
index df2dda7..7adb96b 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -90,16 +90,17 @@
*/
typedef struct buftag
{
- RelFileNode rnode; /* physical relation identifier */
+ RelFileNodeBackend rnode; /* physical relation identifier */
ForkNumber forkNum;
BlockNumber blockNum; /* blknum relative to begin of reln */
} BufferTag;
#define CLEAR_BUFFERTAG(a) \
( \
- (a).rnode.spcNode = InvalidOid, \
- (a).rnode.dbNode = InvalidOid, \
- (a).rnode.relNode = InvalidOid, \
+ (a).rnode.node.spcNode = InvalidOid, \
+ (a).rnode.node.dbNode = InvalidOid, \
+ (a).rnode.node.relNode = InvalidOid, \
+ (a).rnode.backend = InvalidBackendId, \
(a).forkNum = InvalidForkNumber, \
(a).blockNum = InvalidBlockNumber \
)
@@ -113,7 +114,7 @@ typedef struct buftag
#define BUFFERTAGS_EQUAL(a,b) \
( \
- RelFileNodeEquals((a).rnode, (b).rnode) && \
+ RelFileNodeBackendEquals((a).rnode, (b).rnode) && \
(a).blockNum == (b).blockNum && \
(a).forkNum == (b).forkNum \
)
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 509f4b7..3315fa0 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -205,7 +205,7 @@ extern XLogRecPtr BufferGetLSNAtomic(Buffer buffer);
extern void PrintPinnedBufs(void);
#endif
extern Size BufferShmemSize(void);
-extern void BufferGetTag(Buffer buffer, RelFileNode *rnode,
+extern void BufferGetTag(Buffer buffer, RelFileNodeBackend *rnode,
ForkNumber *forknum, BlockNumber *blknum);
extern void MarkBufferDirtyHint(Buffer buffer, bool buffer_std);
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 b0fe19e..b361851 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -328,6 +328,17 @@ 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
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 74b5077..44df4e0 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -85,3 +85,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..b7bf067
--- /dev/null
+++ b/src/test/regress/expected/global_temp.out
@@ -0,0 +1,323 @@
+--
+-- 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)
+
+DROP TABLE global_temptest2;
+DROP TABLE global_temptest1;
+-- Unsupported ON COMMIT and foreign key combination
+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;
+-- Test two phase commit
+CREATE TABLE global_temptest_persistent(col int);
+CREATE GLOBAL TEMP TABLE global_temptest(col int);
+INSERT INTO global_temptest VALUES (1);
+BEGIN;
+INSERT INTO global_temptest VALUES (2);
+SELECT * FROM global_temptest;
+ col
+-----
+ 1
+ 2
+(2 rows)
+
+PREPARE TRANSACTION 'global_temp1';
+-- We can't see anything from an uncommitted transaction
+SELECT * FROM global_temptest;
+ col
+-----
+ 1
+(1 row)
+
+BEGIN;
+INSERT INTO global_temptest VALUES (3);
+INSERT INTO global_temptest_persistent SELECT * FROM global_temptest;
+PREPARE TRANSACTION 'global_temp2';
+COMMIT PREPARED 'global_temp1';
+-- 1, 2
+SELECT * FROM global_temptest;
+ col
+-----
+ 1
+ 2
+(2 rows)
+
+-- Nothing
+SELECT * FROM global_temptest_persistent;
+ col
+-----
+(0 rows)
+
+\c
+-- The temp table is empty now.
+SELECT * FROM global_temptest;
+ col
+-----
+(0 rows)
+
+-- Still nothing in global_temptest_persistent table;
+SELECT * FROM global_temptest_persistent;
+ col
+-----
+(0 rows)
+
+INSERT INTO global_temptest VALUES (4);
+COMMIT PREPARED 'global_temp2';
+-- Only 4
+SELECT * FROM global_temptest;
+ col
+-----
+ 4
+(1 row)
+
+-- 1, 3
+SELECT * FROM global_temptest_persistent;
+ col
+-----
+ 1
+ 3
+(2 rows)
+
+\c
+DROP TABLE global_temptest;
+DROP TABLE global_temptest_persistent;
diff --git a/src/test/regress/expected/global_temp_0.out b/src/test/regress/expected/global_temp_0.out
new file mode 100644
index 0000000..934e751
--- /dev/null
+++ b/src/test/regress/expected/global_temp_0.out
@@ -0,0 +1,326 @@
+--
+-- 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)
+
+DROP TABLE global_temptest2;
+DROP TABLE global_temptest1;
+-- Unsupported ON COMMIT and foreign key combination
+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;
+-- Test two phase commit
+CREATE TABLE global_temptest_persistent(col int);
+CREATE GLOBAL TEMP TABLE global_temptest(col int);
+INSERT INTO global_temptest VALUES (1);
+BEGIN;
+INSERT INTO global_temptest VALUES (2);
+SELECT * FROM global_temptest;
+ col
+-----
+ 1
+ 2
+(2 rows)
+
+PREPARE TRANSACTION 'global_temp1';
+ERROR: prepared transactions are disabled
+HINT: Set max_prepared_transactions to a nonzero value.
+-- We can't see anything from an uncommitted transaction
+SELECT * FROM global_temptest;
+ col
+-----
+ 1
+(1 row)
+
+BEGIN;
+INSERT INTO global_temptest VALUES (3);
+INSERT INTO global_temptest_persistent SELECT * FROM global_temptest;
+PREPARE TRANSACTION 'global_temp2';
+ERROR: prepared transactions are disabled
+HINT: Set max_prepared_transactions to a nonzero value.
+COMMIT PREPARED 'global_temp1';
+ERROR: prepared transaction with identifier "global_temp1" does not exist
+-- 1, 2
+SELECT * FROM global_temptest;
+ col
+-----
+ 1
+(1 row)
+
+-- Nothing
+SELECT * FROM global_temptest_persistent;
+ col
+-----
+(0 rows)
+
+\c
+-- The temp table is empty now.
+SELECT * FROM global_temptest;
+ col
+-----
+(0 rows)
+
+-- Still nothing in global_temptest_persistent table;
+SELECT * FROM global_temptest_persistent;
+ col
+-----
+(0 rows)
+
+INSERT INTO global_temptest VALUES (4);
+COMMIT PREPARED 'global_temp2';
+ERROR: prepared transaction with identifier "global_temp2" does not exist
+-- Only 4
+SELECT * FROM global_temptest;
+ col
+-----
+ 4
+(1 row)
+
+-- 1, 3
+SELECT * FROM global_temptest_persistent;
+ col
+-----
+(0 rows)
+
+\c
+DROP TABLE global_temptest;
+DROP TABLE global_temptest_persistent;
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..4d2da8d
--- /dev/null
+++ b/src/test/regress/sql/global_temp.sql
@@ -0,0 +1,191 @@
+--
+-- 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;
+
+DROP TABLE global_temptest2;
+DROP TABLE global_temptest1;
+
+-- Unsupported ON COMMIT and foreign key combination
+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;
+
+-- Test two phase commit
+CREATE TABLE global_temptest_persistent(col int);
+CREATE GLOBAL TEMP TABLE global_temptest(col int);
+INSERT INTO global_temptest VALUES (1);
+
+BEGIN;
+INSERT INTO global_temptest VALUES (2);
+SELECT * FROM global_temptest;
+PREPARE TRANSACTION 'global_temp1';
+-- We can't see anything from an uncommitted transaction
+SELECT * FROM global_temptest;
+
+BEGIN;
+INSERT INTO global_temptest VALUES (3);
+INSERT INTO global_temptest_persistent SELECT * FROM global_temptest;
+PREPARE TRANSACTION 'global_temp2';
+COMMIT PREPARED 'global_temp1';
+-- 1, 2
+SELECT * FROM global_temptest;
+-- Nothing
+SELECT * FROM global_temptest_persistent;
+\c
+-- The temp table is empty now.
+SELECT * FROM global_temptest;
+-- Still nothing in global_temptest_persistent table;
+SELECT * FROM global_temptest_persistent;
+INSERT INTO global_temptest VALUES (4);
+COMMIT PREPARED 'global_temp2';
+-- Only 4
+SELECT * FROM global_temptest;
+-- 1, 3
+SELECT * FROM global_temptest_persistent;
+\c
+DROP TABLE global_temptest;
+DROP TABLE global_temptest_persistent;
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;
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-09-20 16:43 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
1 sibling, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2019-09-20 16:43 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
st 18. 9. 2019 v 12:04 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:
>
>
> On 21.08.2019 11:54, Konstantin Knizhnik wrote:
>
>
>
> On 20.08.2019 20:01, Pavel Stehule wrote:
>
> Another solution is wait on ZHeap storage and replica can to have own UNDO
> log.
>
>>
>> I thought about implementation of special table access method for
>> temporary tables.
>>
>
> +1
>
>
> Unfortunately implementing special table access method for temporary
> tables doesn't solve all problems.
> XID generation is not part of table access methods.
> So we still need to assign some XID to write transaction at replica which
> will not conflict with XIDs received from master.
> Actually only global temp tables can be updated at replica and so assigned
> XIDs can be stored only in tuples of such relations.
> But still I am not sure that we can use arbitrary XID for such
> transactions at replica.
>
> Also I upset by amount of functionality which has to be reimplemented for
> global temp tables if we really want to provide access method for them:
>
> 1. CLOG
> 2. vacuum
> 3. MVCC visibility
>
> And still it is not possible to encapsulate all changes need to support
> writes to temp tables at replica inside table access method.
> XID assignment, transaction commit and abort, subtransactions - all this
> places need to be patched.
>
>
> I was able to fully support work with global temp tables at replica
> (including subtransactions).
> The patch is attached. Also you can find this version in
> https://github.com/postgrespro/postgresql.builtin_pool/tree/global_temp_hot
>
> Right now transactions at replica updating global temp table are assigned
> special kind of GIDs which are not related with XIDs received from master.
> So special visibility rules are used for such tables at replica. Also I
> have to patch TransactionIdIsInProgress, TransactionIdDidCommit,
> TransactionIdGetCurrent
> functions to correctly handle such XIDs. In principle it is possible to
> implement global temp tables as special heap access method. But it will
> require copying a lot of code (heapam.c)
> so I prefer to add few checks to existed functions.
>
> There are still some limitations:
> - Number of transactions at replica which update temp tables is limited by
> 2^32 (wraparound problem is not addressed).
> - I have to maintain in-memory analog of CLOG for such transactions which
> is also not cropped. It means that for 2^32 transaction size of bitmap can
> grow up to 0.5Gb.
>
> I try to understand what are the following steps in global temp tables
> support.
> This is why I want to perform short survey - what people are expecting
> from global temp tables:
>
> 1. I do not need them at all.
> 2. Eliminate catalog bloating.
> 3. Mostly needed for compatibility with Oracle (simplify porting,...).
> 4. Parallel query execution.
> 5. Can be used at replica.
> 6. More efficient use of resources (first of all memory).
>
There can be other point important for cloud. Inside some cloud usually
there are two types of discs - persistent (slow) and ephemeral (fast). We
effectively used temp tables there because we moved temp tablespace to
ephemeral discs.
I missing one point in your list - developer's comfort - using temp tables
is just much more comfortable - you don't need create it again, again, ..
Due this behave is possible to reduce @2 and @3 can be nice side effect. If
you reduce @2 to zero, then @5 should be possible without any other.
Pavel
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-09-23 07:57 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-09-23 07:57 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
On 20.09.2019 19:43, Pavel Stehule wrote:
>
> 1. I do not need them at all.
> 2. Eliminate catalog bloating.
> 3. Mostly needed for compatibility with Oracle (simplify porting,...).
> 4. Parallel query execution.
> 5. Can be used at replica.
> 6. More efficient use of resources (first of all memory).
>
>
> There can be other point important for cloud. Inside some cloud
> usually there are two types of discs - persistent (slow) and ephemeral
> (fast). We effectively used temp tables there because we moved temp
> tablespace to ephemeral discs.
Yes, I already heard this argument and agree with it.
I just want to notice two things:
1. My assumption is that in most cases data of temporary table can fit
in memory (certainly if we are not limiting them by temp_buffers = 8MB,
but store in shared buffers) and so there is on need to write them to
the persistent media at all.
2. Global temp tables do not substitute local temp tables, accessed
through local buffers. So if you want to use temporary storage, you will
always have a way to do it.
The question is whether we need to support two kinds of global temp
tables (with shared or private buffers) or just implement one of them.
>
> I missing one point in your list - developer's comfort - using temp
> tables is just much more comfortable - you don't need create it again,
> again, .. Due this behave is possible to reduce @2 and @3 can be nice
> side effect. If you reduce @2 to zero, then @5 should be possible
> without any other.
>
Sorry, I do not completely understand your point here
You can use normal (permanent) table and you will not have to create
them again and again. It is also possible to use them for storing
temporary data - just need to truncate table when data is not needed any
more.
Certainly you can not use the same table in more than one backend. Here
is the main advantage of temp tables - you can have storage of
per-session data and do not worry about possible name conflicts.
From the other side: there are many cases where format of temporary
data is not statically known: it is determined dynamically during
program execution.
In this case local temp table provides the most convenient mechanism for
working with such data.
This is why I think that ewe need to have both local and global temp tables.
Also I do not agree with your statement "If you reduce @2 to zero, then
@5 should be possible without any other".
In the solution implemented by Aleksander Alekseev metadata of temporary
tables is kept in memory and not affecting catalog at all.
But them still can not be used at replica.
There are still some serious problems which need to be fixed to able it:
allow insert/update/delete statements for read-only transactions,
somehow assign XIDs for them, implement savepoints and rollback of such
transactions.
All this was done in the last version of my patch.
Yes, it doesn't depend on whether we are using shared or private buffers
for temporary tables. The same approach can be implemented for both of them.
The question is whether we are really need temp tables at replica and if
so, do we need full transaction support for them, including rollbacks,
subtransactions.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-09-23 16:50 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 0 replies; 525+ messages in thread
From: Pavel Stehule @ 2019-09-23 16:50 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
po 23. 9. 2019 v 9:57 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:
>
>
> On 20.09.2019 19:43, Pavel Stehule wrote:
>
>
> 1. I do not need them at all.
>> 2. Eliminate catalog bloating.
>> 3. Mostly needed for compatibility with Oracle (simplify porting,...).
>> 4. Parallel query execution.
>> 5. Can be used at replica.
>> 6. More efficient use of resources (first of all memory).
>>
>
> There can be other point important for cloud. Inside some cloud usually
> there are two types of discs - persistent (slow) and ephemeral (fast). We
> effectively used temp tables there because we moved temp tablespace to
> ephemeral discs.
>
>
> Yes, I already heard this argument and agree with it.
> I just want to notice two things:
> 1. My assumption is that in most cases data of temporary table can fit in
> memory (certainly if we are not limiting them by temp_buffers = 8MB, but
> store in shared buffers) and so there is on need to write them to the
> persistent media at all.
> 2. Global temp tables do not substitute local temp tables, accessed
> through local buffers. So if you want to use temporary storage, you will
> always have a way to do it.
> The question is whether we need to support two kinds of global temp tables
> (with shared or private buffers) or just implement one of them.
>
It's valid only for OLTP. OLAP world is totally different. More if all
users used temporary tables, and you should to calculate with it - it is
one reason for global temp tables, then you need multiply size by
max_connection.
hard to say what is best from implementation perspective, but it can be
unhappy if global temporary tables has different performance
characteristics and configuration than local temporary tables.
>
>
> I missing one point in your list - developer's comfort - using temp tables
> is just much more comfortable - you don't need create it again, again, ..
> Due this behave is possible to reduce @2 and @3 can be nice side effect. If
> you reduce @2 to zero, then @5 should be possible without any other.
>
> Sorry, I do not completely understand your point here
> You can use normal (permanent) table and you will not have to create them
> again and again. It is also possible to use them for storing temporary data
> - just need to truncate table when data is not needed any more.
> Certainly you can not use the same table in more than one backend. Here is
> the main advantage of temp tables - you can have storage of per-session
> data and do not worry about possible name conflicts.
>
You use temporary tables because you know so you share data between session
never. I don't remember any situation when I designed temp tables with
different schema for different sessions.
Using global temp table is not effective - you are work with large tables,
you need to use delete, .. so you cannot to use classic table like temp
tables effectively.
> From the other side: there are many cases where format of temporary data
> is not statically known: it is determined dynamically during program
> execution.
> In this case local temp table provides the most convenient mechanism for
> working with such data.
>
> This is why I think that ewe need to have both local and global temp
> tables.
>
> Also I do not agree with your statement "If you reduce @2 to zero, then @5
> should be possible without any other".
> In the solution implemented by Aleksander Alekseev metadata of temporary
> tables is kept in memory and not affecting catalog at all.
> But them still can not be used at replica.
> There are still some serious problems which need to be fixed to able it:
> allow insert/update/delete statements for read-only transactions, somehow
> assign XIDs for them, implement savepoints and rollback of such
> transactions.
> All this was done in the last version of my patch.
> Yes, it doesn't depend on whether we are using shared or private buffers
> for temporary tables. The same approach can be implemented for both of them.
> The question is whether we are really need temp tables at replica and if
> so, do we need full transaction support for them, including rollbacks,
> subtransactions.
>
temporary tables (of any type) on replica is interesting feature that opens
some possibilities. Some queries cannot be optimized and should be divided
and some results should be stored to temporary tables, analysed (to get
correct statistics), maybe indexed, and after that the calculation can
continue. Now you can do this just only on master. More - on HotStandBy the
data are read only, and without direct impact on master (production), so
you can do some harder calculation there. And temporary tables is used
technique how to fix estimation errors.
I don't think so subtransaction, transaction, rollbacks are necessary for
these tables. On second hand with out it, it is half cooked features, and
can looks pretty strange in pg environment.
I am very happy, how much work you do in this area, I had not a courage to
start this job, but I don't think so this work can be reduced just to some
supported scenarios - and I hope so correct implementation is possible -
although it is not simply work.
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-09-25 20:28 Alvaro Herrera <alvherre@2ndquadrant.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Alvaro Herrera @ 2019-09-25 20:28 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
This broke recently. Can you please rebase?
--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-09-26 07:05 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Alvaro Herrera <alvherre@2ndquadrant.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-09-26 07:05 UTC (permalink / raw)
To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
On 25.09.2019 23:28, Alvaro Herrera wrote:
> This broke recently. Can you please rebase?
>
Rebased version of the patch is attached.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
[text/x-patch] global_shared_temp_replica-3.patch (114.4K, ../../07b156c7-aa8e-28bc-9d4a-2a5c45e922d2@postgrespro.ru/2-global_shared_temp_replica-3.patch)
download | inline diff:
diff --git a/contrib/pg_buffercache/pg_buffercache_pages.c b/contrib/pg_buffercache/pg_buffercache_pages.c
index 1bd579f..2d93f6f 100644
--- a/contrib/pg_buffercache/pg_buffercache_pages.c
+++ b/contrib/pg_buffercache/pg_buffercache_pages.c
@@ -153,9 +153,9 @@ pg_buffercache_pages(PG_FUNCTION_ARGS)
buf_state = LockBufHdr(bufHdr);
fctx->record[i].bufferid = BufferDescriptorGetBuffer(bufHdr);
- fctx->record[i].relfilenode = bufHdr->tag.rnode.relNode;
- fctx->record[i].reltablespace = bufHdr->tag.rnode.spcNode;
- fctx->record[i].reldatabase = bufHdr->tag.rnode.dbNode;
+ fctx->record[i].relfilenode = bufHdr->tag.rnode.node.relNode;
+ fctx->record[i].reltablespace = bufHdr->tag.rnode.node.spcNode;
+ fctx->record[i].reldatabase = bufHdr->tag.rnode.node.dbNode;
fctx->record[i].forknum = bufHdr->tag.forkNum;
fctx->record[i].blocknum = bufHdr->tag.blockNum;
fctx->record[i].usagecount = BUF_STATE_GET_USAGECOUNT(buf_state);
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 38ae240..8a04954 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -608,9 +608,9 @@ apw_dump_now(bool is_bgworker, bool dump_unlogged)
if (buf_state & BM_TAG_VALID &&
((buf_state & BM_PERMANENT) || dump_unlogged))
{
- block_info_array[num_blocks].database = bufHdr->tag.rnode.dbNode;
- block_info_array[num_blocks].tablespace = bufHdr->tag.rnode.spcNode;
- block_info_array[num_blocks].filenode = bufHdr->tag.rnode.relNode;
+ block_info_array[num_blocks].database = bufHdr->tag.rnode.node.dbNode;
+ block_info_array[num_blocks].tablespace = bufHdr->tag.rnode.node.spcNode;
+ block_info_array[num_blocks].filenode = bufHdr->tag.rnode.node.relNode;
block_info_array[num_blocks].forknum = bufHdr->tag.forkNum;
block_info_array[num_blocks].blocknum = bufHdr->tag.blockNum;
++num_blocks;
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index a2c44a9..43b4c66 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -158,7 +158,8 @@ pgrowlocks(PG_FUNCTION_ARGS)
/* must hold a buffer lock to call HeapTupleSatisfiesUpdate */
LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
- htsu = HeapTupleSatisfiesUpdate(tuple,
+ htsu = HeapTupleSatisfiesUpdate(mydata->rel,
+ tuple,
GetCurrentCommandId(false),
hscan->rs_cbuf);
xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 70af43e..9cce720 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -349,7 +349,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
/* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
- if (HeapTupleSatisfiesVisibility(tuple, &SnapshotDirty, hscan->rs_cbuf))
+ if (HeapTupleSatisfiesVisibility(rel, tuple, &SnapshotDirty, hscan->rs_cbuf))
{
stat.tuple_len += tuple->t_len;
stat.tuple_count++;
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/gin/ginxlog.c b/src/backend/access/gin/ginxlog.c
index c945b28..14d4e48 100644
--- a/src/backend/access/gin/ginxlog.c
+++ b/src/backend/access/gin/ginxlog.c
@@ -95,13 +95,13 @@ ginRedoInsertEntry(Buffer buffer, bool isLeaf, BlockNumber rightblkno, void *rda
if (PageAddItem(page, (Item) itup, IndexTupleSize(itup), offset, false, false) == InvalidOffsetNumber)
{
- RelFileNode node;
+ RelFileNodeBackend rnode;
ForkNumber forknum;
BlockNumber blknum;
- BufferGetTag(buffer, &node, &forknum, &blknum);
+ BufferGetTag(buffer, &rnode, &forknum, &blknum);
elog(ERROR, "failed to add item to index page in %u/%u/%u",
- node.spcNode, node.dbNode, node.relNode);
+ rnode.node.spcNode, rnode.node.dbNode, rnode.node.relNode);
}
}
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.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/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/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/varsup.c b/src/backend/access/transam/varsup.c
index b18eee4..fb3bebc 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -71,7 +71,7 @@ GetNewTransactionId(bool isSubXact)
/* safety check, we should never get this far in a HS standby */
if (RecoveryInProgress())
- elog(ERROR, "cannot assign TransactionIds during recovery");
+ elog(ERROR, "cannot assign TransactionIds during recovery");
LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
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();
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index 3ec67d4..edec8ca 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -213,6 +213,7 @@ void
XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
{
registered_buffer *regbuf;
+ RelFileNodeBackend rnode;
/* NO_IMAGE doesn't make sense with FORCE_IMAGE */
Assert(!((flags & REGBUF_FORCE_IMAGE) && (flags & (REGBUF_NO_IMAGE))));
@@ -227,7 +228,8 @@ XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
regbuf = ®istered_buffers[block_id];
- BufferGetTag(buffer, ®buf->rnode, ®buf->forkno, ®buf->block);
+ BufferGetTag(buffer, &rnode, ®buf->forkno, ®buf->block);
+ regbuf->rnode = rnode.node;
regbuf->page = BufferGetPage(buffer);
regbuf->flags = flags;
regbuf->rdata_tail = (XLogRecData *) ®buf->rdata_head;
@@ -919,7 +921,7 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std)
int flags;
PGAlignedBlock copied_buffer;
char *origdata = (char *) BufferGetBlock(buffer);
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forkno;
BlockNumber blkno;
@@ -948,7 +950,7 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std)
flags |= REGBUF_STANDARD;
BufferGetTag(buffer, &rnode, &forkno, &blkno);
- XLogRegisterBlock(0, &rnode, forkno, blkno, copied_buffer.data, flags);
+ XLogRegisterBlock(0, &rnode.node, forkno, blkno, copied_buffer.data, flags);
recptr = XLogInsert(RM_XLOG_ID, XLOG_FPI_FOR_HINT);
}
@@ -1009,7 +1011,7 @@ XLogRecPtr
log_newpage_buffer(Buffer buffer, bool page_std)
{
Page page = BufferGetPage(buffer);
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forkNum;
BlockNumber blkno;
@@ -1018,7 +1020,7 @@ log_newpage_buffer(Buffer buffer, bool page_std)
BufferGetTag(buffer, &rnode, &forkNum, &blkno);
- return log_newpage(&rnode, forkNum, blkno, page, page_std);
+ return log_newpage(&rnode.node, forkNum, blkno, page, page_std);
}
/*
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 098732c..cb35c72 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3641,7 +3641,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 b8c9b6f..b06ff28 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 05593f3..73be898 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -586,7 +586,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")));
@@ -1771,7 +1771,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);
@@ -7677,6 +7678,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,
@@ -14109,6 +14116,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 */
@@ -14596,14 +14610,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/executor/execMain.c b/src/backend/executor/execMain.c
index ea4b586..9cd8361 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_SESSION)
+ continue;
+
PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index cf17614..d673bd7 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -127,7 +127,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
relation = table_open(relationObjectId, NoLock);
/* Temporary and unlogged relations are inaccessible during recovery. */
- if (!RelationNeedsWAL(relation) && RecoveryInProgress())
+ if (!RelationNeedsWAL(relation) && RecoveryInProgress() && relation->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot access temporary or unlogged relations during recovery")));
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 073f313..5760a9c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2154,7 +2154,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/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 8ce28ad..a5d2de9 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -3487,6 +3487,7 @@ ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
{
ReorderBufferTupleCidKey key;
ReorderBufferTupleCidEnt *ent;
+ RelFileNodeBackend rnode;
ForkNumber forkno;
BlockNumber blockno;
bool updated_mapping = false;
@@ -3500,7 +3501,8 @@ ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
* get relfilenode from the buffer, no convenient way to access it other
* than that.
*/
- BufferGetTag(buffer, &key.relnode, &forkno, &blockno);
+ BufferGetTag(buffer, &rnode, &forkno, &blockno);
+ key.relnode = rnode.node;
/* tuples can only be in the main fork */
Assert(forkno == MAIN_FORKNUM);
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 483f705..cdba076 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -556,7 +556,7 @@ PrefetchBuffer(Relation reln, ForkNumber forkNum, BlockNumber blockNum)
int buf_id;
/* create a tag so we can lookup the buffer */
- INIT_BUFFERTAG(newTag, reln->rd_smgr->smgr_rnode.node,
+ INIT_BUFFERTAG(newTag, reln->rd_smgr->smgr_rnode,
forkNum, blockNum);
/* determine its hash code and partition lock ID */
@@ -710,7 +710,7 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
Block bufBlock;
bool found;
bool isExtend;
- bool isLocalBuf = SmgrIsTemp(smgr);
+ bool isLocalBuf = SmgrIsTemp(smgr) && relpersistence == RELPERSISTENCE_TEMP;
*hit = false;
@@ -1010,7 +1010,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
uint32 buf_state;
/* create a tag so we can lookup the buffer */
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* determine its hash code and partition lock ID */
newHash = BufTableHashCode(&newTag);
@@ -1532,7 +1532,8 @@ ReleaseAndReadBuffer(Buffer buffer,
{
bufHdr = GetLocalBufferDescriptor(-buffer - 1);
if (bufHdr->tag.blockNum == blockNum &&
- RelFileNodeEquals(bufHdr->tag.rnode, relation->rd_node) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, relation->rd_node) &&
+ bufHdr->tag.rnode.backend == relation->rd_backend &&
bufHdr->tag.forkNum == forkNum)
return buffer;
ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
@@ -1543,7 +1544,8 @@ ReleaseAndReadBuffer(Buffer buffer,
bufHdr = GetBufferDescriptor(buffer - 1);
/* we have pin, so it's ok to examine tag without spinlock */
if (bufHdr->tag.blockNum == blockNum &&
- RelFileNodeEquals(bufHdr->tag.rnode, relation->rd_node) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, relation->rd_node) &&
+ bufHdr->tag.rnode.backend == relation->rd_backend &&
bufHdr->tag.forkNum == forkNum)
return buffer;
UnpinBuffer(bufHdr, true);
@@ -1845,8 +1847,8 @@ BufferSync(int flags)
item = &CkptBufferIds[num_to_scan++];
item->buf_id = buf_id;
- item->tsId = bufHdr->tag.rnode.spcNode;
- item->relNode = bufHdr->tag.rnode.relNode;
+ item->tsId = bufHdr->tag.rnode.node.spcNode;
+ item->relNode = bufHdr->tag.rnode.node.relNode;
item->forkNum = bufHdr->tag.forkNum;
item->blockNum = bufHdr->tag.blockNum;
}
@@ -2559,7 +2561,7 @@ PrintBufferLeakWarning(Buffer buffer)
}
/* theoretically we should lock the bufhdr here */
- path = relpathbackend(buf->tag.rnode, backend, buf->tag.forkNum);
+ path = relpathbackend(buf->tag.rnode.node, backend, buf->tag.forkNum);
buf_state = pg_atomic_read_u32(&buf->state);
elog(WARNING,
"buffer refcount leak: [%03d] "
@@ -2631,7 +2633,7 @@ BufferGetBlockNumber(Buffer buffer)
* a buffer.
*/
void
-BufferGetTag(Buffer buffer, RelFileNode *rnode, ForkNumber *forknum,
+BufferGetTag(Buffer buffer, RelFileNodeBackend *rnode, ForkNumber *forknum,
BlockNumber *blknum)
{
BufferDesc *bufHdr;
@@ -2696,7 +2698,7 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
/* Find smgr relation for buffer */
if (reln == NULL)
- reln = smgropen(buf->tag.rnode, InvalidBackendId);
+ reln = smgropen(buf->tag.rnode.node, buf->tag.rnode.backend);
TRACE_POSTGRESQL_BUFFER_FLUSH_START(buf->tag.forkNum,
buf->tag.blockNum,
@@ -2931,7 +2933,7 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber *forkNum,
int j;
/* If it's a local relation, it's localbuf.c's problem. */
- if (RelFileNodeBackendIsTemp(rnode))
+ if (RelFileNodeBackendIsLocalTemp(rnode))
{
if (rnode.backend == MyBackendId)
{
@@ -2963,14 +2965,13 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber *forkNum,
* We could check forkNum and blockNum as well as the rnode, but the
* incremental win from doing so seems small.
*/
- if (!RelFileNodeEquals(bufHdr->tag.rnode, rnode.node))
+ if (!RelFileNodeBackendEquals(bufHdr->tag.rnode, rnode))
continue;
buf_state = LockBufHdr(bufHdr);
-
for (j = 0; j < nforks; j++)
{
- if (RelFileNodeEquals(bufHdr->tag.rnode, rnode.node) &&
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, rnode) &&
bufHdr->tag.forkNum == forkNum[j] &&
bufHdr->tag.blockNum >= firstDelBlock[j])
{
@@ -2997,24 +2998,24 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
{
int i,
n = 0;
- RelFileNode *nodes;
+ RelFileNodeBackend *nodes;
bool use_bsearch;
if (nnodes == 0)
return;
- nodes = palloc(sizeof(RelFileNode) * nnodes); /* non-local relations */
+ nodes = palloc(sizeof(RelFileNodeBackend) * nnodes); /* non-local relations */
/* If it's a local relation, it's localbuf.c's problem. */
for (i = 0; i < nnodes; i++)
{
- if (RelFileNodeBackendIsTemp(rnodes[i]))
+ if (RelFileNodeBackendIsLocalTemp(rnodes[i]))
{
if (rnodes[i].backend == MyBackendId)
DropRelFileNodeAllLocalBuffers(rnodes[i].node);
}
else
- nodes[n++] = rnodes[i].node;
+ nodes[n++] = rnodes[i];
}
/*
@@ -3037,11 +3038,11 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
/* sort the list of rnodes if necessary */
if (use_bsearch)
- pg_qsort(nodes, n, sizeof(RelFileNode), rnode_comparator);
+ pg_qsort(nodes, n, sizeof(RelFileNodeBackend), rnode_comparator);
for (i = 0; i < NBuffers; i++)
{
- RelFileNode *rnode = NULL;
+ RelFileNodeBackend *rnode = NULL;
BufferDesc *bufHdr = GetBufferDescriptor(i);
uint32 buf_state;
@@ -3056,7 +3057,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
for (j = 0; j < n; j++)
{
- if (RelFileNodeEquals(bufHdr->tag.rnode, nodes[j]))
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, nodes[j]))
{
rnode = &nodes[j];
break;
@@ -3066,7 +3067,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
else
{
rnode = bsearch((const void *) &(bufHdr->tag.rnode),
- nodes, n, sizeof(RelFileNode),
+ nodes, n, sizeof(RelFileNodeBackend),
rnode_comparator);
}
@@ -3075,7 +3076,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
continue;
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, (*rnode)))
+ if (RelFileNodeBackendEquals(bufHdr->tag.rnode, (*rnode)))
InvalidateBuffer(bufHdr); /* releases spinlock */
else
UnlockBufHdr(bufHdr, buf_state);
@@ -3114,11 +3115,11 @@ DropDatabaseBuffers(Oid dbid)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (bufHdr->tag.rnode.dbNode != dbid)
+ if (bufHdr->tag.rnode.node.dbNode != dbid)
continue;
buf_state = LockBufHdr(bufHdr);
- if (bufHdr->tag.rnode.dbNode == dbid)
+ if (bufHdr->tag.rnode.node.dbNode == dbid)
InvalidateBuffer(bufHdr); /* releases spinlock */
else
UnlockBufHdr(bufHdr, buf_state);
@@ -3148,7 +3149,7 @@ PrintBufferDescs(void)
"[%02d] (freeNext=%d, rel=%s, "
"blockNum=%u, flags=0x%x, refcount=%u %d)",
i, buf->freeNext,
- relpathbackend(buf->tag.rnode, InvalidBackendId, buf->tag.forkNum),
+ relpath(buf->tag.rnode, buf->tag.forkNum),
buf->tag.blockNum, buf->flags,
buf->refcount, GetPrivateRefCount(b));
}
@@ -3216,7 +3217,8 @@ FlushRelationBuffers(Relation rel)
uint32 buf_state;
bufHdr = GetLocalBufferDescriptor(i);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+ if (RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node) &&
+ bufHdr->tag.rnode.backend == rel->rd_backend &&
((buf_state = pg_atomic_read_u32(&bufHdr->state)) &
(BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
@@ -3263,13 +3265,15 @@ FlushRelationBuffers(Relation rel)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
+ if (!RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node)
+ || bufHdr->tag.rnode.backend != rel->rd_backend)
continue;
ReservePrivateRefCountEntry();
buf_state = LockBufHdr(bufHdr);
- if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+ if (RelFileNodeEquals(bufHdr->tag.rnode.node, rel->rd_node) &&
+ bufHdr->tag.rnode.backend == rel->rd_backend &&
(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
PinBuffer_Locked(bufHdr);
@@ -3317,13 +3321,13 @@ FlushDatabaseBuffers(Oid dbid)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
- if (bufHdr->tag.rnode.dbNode != dbid)
+ if (bufHdr->tag.rnode.node.dbNode != dbid)
continue;
ReservePrivateRefCountEntry();
buf_state = LockBufHdr(bufHdr);
- if (bufHdr->tag.rnode.dbNode == dbid &&
+ if (bufHdr->tag.rnode.node.dbNode == dbid &&
(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
PinBuffer_Locked(bufHdr);
@@ -4063,7 +4067,7 @@ AbortBufferIO(void)
/* Buffer is pinned, so we can read tag without spinlock */
char *path;
- path = relpathperm(buf->tag.rnode, buf->tag.forkNum);
+ path = relpath(buf->tag.rnode, buf->tag.forkNum);
ereport(WARNING,
(errcode(ERRCODE_IO_ERROR),
errmsg("could not write block %u of %s",
@@ -4087,7 +4091,7 @@ shared_buffer_write_error_callback(void *arg)
/* Buffer is pinned, so we can read the tag without locking the spinlock */
if (bufHdr != NULL)
{
- char *path = relpathperm(bufHdr->tag.rnode, bufHdr->tag.forkNum);
+ char *path = relpath(bufHdr->tag.rnode, bufHdr->tag.forkNum);
errcontext("writing block %u of relation %s",
bufHdr->tag.blockNum, path);
@@ -4105,7 +4109,7 @@ local_buffer_write_error_callback(void *arg)
if (bufHdr != NULL)
{
- char *path = relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ char *path = relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum);
errcontext("writing block %u of relation %s",
@@ -4120,22 +4124,27 @@ local_buffer_write_error_callback(void *arg)
static int
rnode_comparator(const void *p1, const void *p2)
{
- RelFileNode n1 = *(const RelFileNode *) p1;
- RelFileNode n2 = *(const RelFileNode *) p2;
+ RelFileNodeBackend n1 = *(const RelFileNodeBackend *) p1;
+ RelFileNodeBackend n2 = *(const RelFileNodeBackend *) p2;
+
+ if (n1.node.relNode < n2.node.relNode)
+ return -1;
+ else if (n1.node.relNode > n2.node.relNode)
+ return 1;
- if (n1.relNode < n2.relNode)
+ if (n1.node.dbNode < n2.node.dbNode)
return -1;
- else if (n1.relNode > n2.relNode)
+ else if (n1.node.dbNode > n2.node.dbNode)
return 1;
- if (n1.dbNode < n2.dbNode)
+ if (n1.node.spcNode < n2.node.spcNode)
return -1;
- else if (n1.dbNode > n2.dbNode)
+ else if (n1.node.spcNode > n2.node.spcNode)
return 1;
- if (n1.spcNode < n2.spcNode)
+ if (n1.backend < n2.backend)
return -1;
- else if (n1.spcNode > n2.spcNode)
+ else if (n1.backend > n2.backend)
return 1;
else
return 0;
@@ -4371,7 +4380,7 @@ IssuePendingWritebacks(WritebackContext *context)
next = &context->pending_writebacks[i + ahead + 1];
/* different file, stop */
- if (!RelFileNodeEquals(cur->tag.rnode, next->tag.rnode) ||
+ if (!RelFileNodeBackendEquals(cur->tag.rnode, next->tag.rnode) ||
cur->tag.forkNum != next->tag.forkNum)
break;
@@ -4390,7 +4399,7 @@ IssuePendingWritebacks(WritebackContext *context)
i += ahead;
/* and finally tell the kernel to write the data to storage */
- reln = smgropen(tag.rnode, InvalidBackendId);
+ reln = smgropen(tag.rnode.node, tag.rnode.backend);
smgrwriteback(reln, tag.forkNum, tag.blockNum, nblocks);
}
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index f5f6a29..6bd5ecb 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -68,7 +68,7 @@ LocalPrefetchBuffer(SMgrRelation smgr, ForkNumber forkNum,
BufferTag newTag; /* identity of requested block */
LocalBufferLookupEnt *hresult;
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* Initialize local buffers if first request in this session */
if (LocalBufHash == NULL)
@@ -111,7 +111,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
bool found;
uint32 buf_state;
- INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
+ INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);
/* Initialize local buffers if first request in this session */
if (LocalBufHash == NULL)
@@ -209,7 +209,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
Page localpage = (char *) LocalBufHdrGetBlock(bufHdr);
/* Find smgr relation for buffer */
- oreln = smgropen(bufHdr->tag.rnode, MyBackendId);
+ oreln = smgropen(bufHdr->tag.rnode.node, MyBackendId);
PageSetChecksumInplace(localpage, bufHdr->tag.blockNum);
@@ -331,14 +331,14 @@ DropRelFileNodeLocalBuffers(RelFileNode rnode, ForkNumber forkNum,
buf_state = pg_atomic_read_u32(&bufHdr->state);
if ((buf_state & BM_TAG_VALID) &&
- RelFileNodeEquals(bufHdr->tag.rnode, rnode) &&
+ RelFileNodeEquals(bufHdr->tag.rnode.node, rnode) &&
bufHdr->tag.forkNum == forkNum &&
bufHdr->tag.blockNum >= firstDelBlock)
{
if (LocalRefCount[i] != 0)
elog(ERROR, "block %u of %s is still referenced (local %u)",
bufHdr->tag.blockNum,
- relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum),
LocalRefCount[i]);
/* Remove entry from hashtable */
@@ -377,12 +377,12 @@ DropRelFileNodeAllLocalBuffers(RelFileNode rnode)
buf_state = pg_atomic_read_u32(&bufHdr->state);
if ((buf_state & BM_TAG_VALID) &&
- RelFileNodeEquals(bufHdr->tag.rnode, rnode))
+ RelFileNodeEquals(bufHdr->tag.rnode.node, rnode))
{
if (LocalRefCount[i] != 0)
elog(ERROR, "block %u of %s is still referenced (local %u)",
bufHdr->tag.blockNum,
- relpathbackend(bufHdr->tag.rnode, MyBackendId,
+ relpathbackend(bufHdr->tag.rnode.node, MyBackendId,
bufHdr->tag.forkNum),
LocalRefCount[i]);
/* Remove entry from hashtable */
diff --git a/src/backend/storage/freespace/fsmpage.c b/src/backend/storage/freespace/fsmpage.c
index cf7f03f..65eb422 100644
--- a/src/backend/storage/freespace/fsmpage.c
+++ b/src/backend/storage/freespace/fsmpage.c
@@ -268,13 +268,13 @@ restart:
*
* Fix the corruption and restart.
*/
- RelFileNode rnode;
+ RelFileNodeBackend rnode;
ForkNumber forknum;
BlockNumber blknum;
BufferGetTag(buf, &rnode, &forknum, &blknum);
elog(DEBUG1, "fixing corrupt FSM block %u, relation %u/%u/%u",
- blknum, rnode.spcNode, rnode.dbNode, rnode.relNode);
+ blknum, rnode.node.spcNode, rnode.node.dbNode, rnode.node.relNode);
/* make sure we hold an exclusive lock */
if (!exclusive_lock_held)
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 8abcfdf..07739e2 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -991,6 +991,9 @@ TransactionIdIsInProgress(TransactionId xid)
int i,
j;
+ if (AccessTempRelationAtReplica)
+ return IsReplicaCurrentTransactionId(xid) && !IsReplicaTransactionAborted(xid);
+
/*
* Don't bother checking a transaction older than RecentXmin; it could not
* possibly still be running. (Note: in particular, this guarantees that
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 07f3c93..204c4cb 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,48 @@ 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)
+ {
+ /* Remove relation pages from shared buffers */
+ DropRelFileNodesAllBuffers(&rel->rnode, 1);
+
+ /* 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 +273,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 +522,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 +546,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 +723,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 +814,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 f01fea5..4162976 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15602,8 +15602,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/access/heapam.h b/src/include/access/heapam.h
index 858bcb6..2f16c58 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -195,9 +195,9 @@ extern void heap_vacuum_rel(Relation onerel,
struct VacuumParams *params, BufferAccessStrategy bstrategy);
/* in heap/heapam_visibility.c */
-extern bool HeapTupleSatisfiesVisibility(HeapTuple stup, Snapshot snapshot,
+extern bool HeapTupleSatisfiesVisibility(Relation relation, HeapTuple stup, Snapshot snapshot,
Buffer buffer);
-extern TM_Result HeapTupleSatisfiesUpdate(HeapTuple stup, CommandId curcid,
+extern TM_Result HeapTupleSatisfiesUpdate(Relation relation, HeapTuple stup, CommandId curcid,
Buffer buffer);
extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTuple stup, TransactionId OldestXmin,
Buffer buffer);
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index d714551..cbe6760 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -41,6 +41,9 @@
extern int DefaultXactIsoLevel;
extern PGDLLIMPORT int XactIsoLevel;
+extern bool AccessTempRelationAtReplica;
+
+
/*
* We implement three isolation levels internally.
* The two stronger ones use one snapshot per database transaction;
@@ -440,4 +443,8 @@ extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+extern TransactionId GetReplicaTransactionId(void);
+extern bool IsReplicaTransactionAborted(TransactionId xid);
+extern bool IsReplicaCurrentTransactionId(TransactionId xid);
+
#endif /* XACT_H */
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/buf_internals.h b/src/include/storage/buf_internals.h
index 6ffe184..efaf362 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -90,16 +90,17 @@
*/
typedef struct buftag
{
- RelFileNode rnode; /* physical relation identifier */
+ RelFileNodeBackend rnode; /* physical relation identifier */
ForkNumber forkNum;
BlockNumber blockNum; /* blknum relative to begin of reln */
} BufferTag;
#define CLEAR_BUFFERTAG(a) \
( \
- (a).rnode.spcNode = InvalidOid, \
- (a).rnode.dbNode = InvalidOid, \
- (a).rnode.relNode = InvalidOid, \
+ (a).rnode.node.spcNode = InvalidOid, \
+ (a).rnode.node.dbNode = InvalidOid, \
+ (a).rnode.node.relNode = InvalidOid, \
+ (a).rnode.backend = InvalidBackendId, \
(a).forkNum = InvalidForkNumber, \
(a).blockNum = InvalidBlockNumber \
)
@@ -113,7 +114,7 @@ typedef struct buftag
#define BUFFERTAGS_EQUAL(a,b) \
( \
- RelFileNodeEquals((a).rnode, (b).rnode) && \
+ RelFileNodeBackendEquals((a).rnode, (b).rnode) && \
(a).blockNum == (b).blockNum && \
(a).forkNum == (b).forkNum \
)
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 17b97f7..1b0ce65 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -205,7 +205,7 @@ extern XLogRecPtr BufferGetLSNAtomic(Buffer buffer);
extern void PrintPinnedBufs(void);
#endif
extern Size BufferShmemSize(void);
-extern void BufferGetTag(Buffer buffer, RelFileNode *rnode,
+extern void BufferGetTag(Buffer buffer, RelFileNodeBackend *rnode,
ForkNumber *forknum, BlockNumber *blknum);
extern void MarkBufferDirtyHint(Buffer buffer, bool buffer_std);
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..4bcfea1 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
{
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 69ae227..95919f8 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -87,3 +87,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..b7bf067
--- /dev/null
+++ b/src/test/regress/expected/global_temp.out
@@ -0,0 +1,323 @@
+--
+-- 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)
+
+DROP TABLE global_temptest2;
+DROP TABLE global_temptest1;
+-- Unsupported ON COMMIT and foreign key combination
+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;
+-- Test two phase commit
+CREATE TABLE global_temptest_persistent(col int);
+CREATE GLOBAL TEMP TABLE global_temptest(col int);
+INSERT INTO global_temptest VALUES (1);
+BEGIN;
+INSERT INTO global_temptest VALUES (2);
+SELECT * FROM global_temptest;
+ col
+-----
+ 1
+ 2
+(2 rows)
+
+PREPARE TRANSACTION 'global_temp1';
+-- We can't see anything from an uncommitted transaction
+SELECT * FROM global_temptest;
+ col
+-----
+ 1
+(1 row)
+
+BEGIN;
+INSERT INTO global_temptest VALUES (3);
+INSERT INTO global_temptest_persistent SELECT * FROM global_temptest;
+PREPARE TRANSACTION 'global_temp2';
+COMMIT PREPARED 'global_temp1';
+-- 1, 2
+SELECT * FROM global_temptest;
+ col
+-----
+ 1
+ 2
+(2 rows)
+
+-- Nothing
+SELECT * FROM global_temptest_persistent;
+ col
+-----
+(0 rows)
+
+\c
+-- The temp table is empty now.
+SELECT * FROM global_temptest;
+ col
+-----
+(0 rows)
+
+-- Still nothing in global_temptest_persistent table;
+SELECT * FROM global_temptest_persistent;
+ col
+-----
+(0 rows)
+
+INSERT INTO global_temptest VALUES (4);
+COMMIT PREPARED 'global_temp2';
+-- Only 4
+SELECT * FROM global_temptest;
+ col
+-----
+ 4
+(1 row)
+
+-- 1, 3
+SELECT * FROM global_temptest_persistent;
+ col
+-----
+ 1
+ 3
+(2 rows)
+
+\c
+DROP TABLE global_temptest;
+DROP TABLE global_temptest_persistent;
diff --git a/src/test/regress/expected/global_temp_0.out b/src/test/regress/expected/global_temp_0.out
new file mode 100644
index 0000000..934e751
--- /dev/null
+++ b/src/test/regress/expected/global_temp_0.out
@@ -0,0 +1,326 @@
+--
+-- 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)
+
+DROP TABLE global_temptest2;
+DROP TABLE global_temptest1;
+-- Unsupported ON COMMIT and foreign key combination
+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;
+-- Test two phase commit
+CREATE TABLE global_temptest_persistent(col int);
+CREATE GLOBAL TEMP TABLE global_temptest(col int);
+INSERT INTO global_temptest VALUES (1);
+BEGIN;
+INSERT INTO global_temptest VALUES (2);
+SELECT * FROM global_temptest;
+ col
+-----
+ 1
+ 2
+(2 rows)
+
+PREPARE TRANSACTION 'global_temp1';
+ERROR: prepared transactions are disabled
+HINT: Set max_prepared_transactions to a nonzero value.
+-- We can't see anything from an uncommitted transaction
+SELECT * FROM global_temptest;
+ col
+-----
+ 1
+(1 row)
+
+BEGIN;
+INSERT INTO global_temptest VALUES (3);
+INSERT INTO global_temptest_persistent SELECT * FROM global_temptest;
+PREPARE TRANSACTION 'global_temp2';
+ERROR: prepared transactions are disabled
+HINT: Set max_prepared_transactions to a nonzero value.
+COMMIT PREPARED 'global_temp1';
+ERROR: prepared transaction with identifier "global_temp1" does not exist
+-- 1, 2
+SELECT * FROM global_temptest;
+ col
+-----
+ 1
+(1 row)
+
+-- Nothing
+SELECT * FROM global_temptest_persistent;
+ col
+-----
+(0 rows)
+
+\c
+-- The temp table is empty now.
+SELECT * FROM global_temptest;
+ col
+-----
+(0 rows)
+
+-- Still nothing in global_temptest_persistent table;
+SELECT * FROM global_temptest_persistent;
+ col
+-----
+(0 rows)
+
+INSERT INTO global_temptest VALUES (4);
+COMMIT PREPARED 'global_temp2';
+ERROR: prepared transaction with identifier "global_temp2" does not exist
+-- Only 4
+SELECT * FROM global_temptest;
+ col
+-----
+ 4
+(1 row)
+
+-- 1, 3
+SELECT * FROM global_temptest_persistent;
+ col
+-----
+(0 rows)
+
+\c
+DROP TABLE global_temptest;
+DROP TABLE global_temptest_persistent;
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..4d2da8d
--- /dev/null
+++ b/src/test/regress/sql/global_temp.sql
@@ -0,0 +1,191 @@
+--
+-- 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;
+
+DROP TABLE global_temptest2;
+DROP TABLE global_temptest1;
+
+-- Unsupported ON COMMIT and foreign key combination
+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;
+
+-- Test two phase commit
+CREATE TABLE global_temptest_persistent(col int);
+CREATE GLOBAL TEMP TABLE global_temptest(col int);
+INSERT INTO global_temptest VALUES (1);
+
+BEGIN;
+INSERT INTO global_temptest VALUES (2);
+SELECT * FROM global_temptest;
+PREPARE TRANSACTION 'global_temp1';
+-- We can't see anything from an uncommitted transaction
+SELECT * FROM global_temptest;
+
+BEGIN;
+INSERT INTO global_temptest VALUES (3);
+INSERT INTO global_temptest_persistent SELECT * FROM global_temptest;
+PREPARE TRANSACTION 'global_temp2';
+COMMIT PREPARED 'global_temp1';
+-- 1, 2
+SELECT * FROM global_temptest;
+-- Nothing
+SELECT * FROM global_temptest_persistent;
+\c
+-- The temp table is empty now.
+SELECT * FROM global_temptest;
+-- Still nothing in global_temptest_persistent table;
+SELECT * FROM global_temptest_persistent;
+INSERT INTO global_temptest VALUES (4);
+COMMIT PREPARED 'global_temp2';
+-- Only 4
+SELECT * FROM global_temptest;
+-- 1, 3
+SELECT * FROM global_temptest_persistent;
+\c
+DROP TABLE global_temptest;
+DROP TABLE global_temptest_persistent;
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;
^ permalink raw reply [nested|flat] 525+ messages in thread
* [Proposal] Global temporary tables
@ 2019-10-11 12:15 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2019-10-11 12:15 UTC (permalink / raw)
To: pgsql-hackers; +Cc: 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
Dear Hackers,
This propose a way to develop global temporary tables in PostgreSQL.
I noticed that there is an "Allow temporary tables to exist as empty by default in all sessions" in the postgresql todolist.
https://wiki.postgresql.org/wiki/Todo <https://wiki.postgresql.org/wiki/Todo;
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 https://www.postgresql.org/message-id/73954ab7-44d3-b37b-81a3-69bdcbb446f7%40postgrespro.ru
(5) A recent implementation and design from Konstantin Knizhnik covered many functions of GTT: https://www.postgresql.org/message-id/attachment/103265/global_private_temp-1.patch <https://www.postgresql.org/message-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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-10-11 13:50 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 3 replies; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-10-11 13:50 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; +Cc: 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On 11.10.2019 15:15, 曾文旌(义从) wrote:
> Dear Hackers,
>
> This propose a way to develop global temporary tables in PostgreSQL.
>
> I noticed that there is an "Allow temporary tables to exist as empty
> by default in all sessions" in the postgresql todolist.
> https://wiki.postgresql.org/wiki/Todo
>
> In recent years, PG community had many discussions about global temp
> table (GTT) support. Previous discussion covered the following topics:
> (1)The main benefit or function: GTT offers features like “persistent
> schema, ephemeral data”, which avoids catalog bloat and reduces
> catalog vacuum.
> (2)Whether follows ANSI concept of temporary tables
> (3)How to deal with statistics, single copy of schema definition, relcache
> (4)More can be seen in
> https://www.postgresql.org/message-id/73954ab7-44d3-b37b-81a3-69bdcbb446f7%40postgrespro.ru
> (5)A recent implementation and design from Konstantin Knizhnik covered
> many functions of GTT:
> https://www.postgresql.org/message-id/attachment/103265/global_private_temp-1.patch
>
> However, as pointed by Konstantin himself, the implementation still
> needs functions related to CLOG, vacuum, and MVCC visibility.
>
Just to clarify.
I have now proposed several different solutions for GTT:
Shared vs. private buffers for GTT:
1. Private buffers. This is least invasive patch, requiring no changes
in relfilenodes.
2. Shared buffers. Requires changing relfilenode but supports parallel
query execution for GTT.
Access to GTT at replica:
1. Access is prohibited (as for original temp tables). No changes at all.
2. Tuples of temp tables are marked with forzen XID. Minimal changes,
rollbacks are not possible.
3. Providing special XIDs for GTT at replica. No changes in CLOG are
required, but special MVCC visibility rules are used for GTT. Current
limitation: number of transactions accessing GTT at replica is limited
by 2^32
and bitmap of correspondent size has to be maintained (tuples of GTT are
not proceeded by vacuum and not frozen, so XID horizon never moved).
So except the limitation mentioned above (which I do not consider as
critical) there is only one problem which was not addressed: maintaining
statistics for GTT.
If all of the following conditions are true:
1) GTT are used in joins
2) There are indexes defined for GTT
3) Size and histogram of GTT in different backends can significantly vary.
4) ANALYZE was explicitly called for GTT
then query execution plan built in one backend will be also used for
other backends where it can be inefficient.
I also do not consider this problem as "show stopper" for adding GTT to
Postgres.
I still do not understand the opinion of community which functionality
of GTT is considered to be most important.
But the patch with local buffers and no replica support is small enough
to become good starting point.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-10-12 05:16 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2019-10-12 05:16 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
pá 11. 10. 2019 v 15:50 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:
>
>
> On 11.10.2019 15:15, 曾文旌(义从) wrote:
>
> Dear Hackers,
>
> This propose a way to develop global temporary tables in PostgreSQL.
>
> I noticed that there is an "Allow temporary tables to exist as empty by
> default in all sessions" in the postgresql todolist.
> https://wiki.postgresql.org/wiki/Todo
>
> In recent years, PG community had many discussions about global temp
> table (GTT) support. Previous discussion covered the following topics:
> (1) The main benefit or function: GTT offers features like “persistent
> schema, ephemeral data”, which avoids catalog bloat and reduces catalog
> vacuum.
> (2) Whether follows ANSI concept of temporary tables
> (3) How to deal with statistics, single copy of schema definition,
> relcache
> (4) More can be seen in
> https://www.postgresql.org/message-id/73954ab7-44d3-b37b-81a3-69bdcbb446f7%40postgrespro.ru
> (5) A recent implementation and design from Konstantin Knizhnik covered
> many functions of GTT:
> https://www.postgresql.org/message-id/attachment/103265/global_private_temp-1.patch
>
> However, as pointed by Konstantin himself, the implementation still needs
> functions related to CLOG, vacuum, and MVCC visibility.
>
>
> Just to clarify.
> I have now proposed several different solutions for GTT:
>
> Shared vs. private buffers for GTT:
> 1. Private buffers. This is least invasive patch, requiring no changes in
> relfilenodes.
> 2. Shared buffers. Requires changing relfilenode but supports parallel
> query execution for GTT.
>
This is important argument for using share buffers. Maybe the best is mix
of both - store files in temporal tablespace, but using share buffers.
More, it can be accessible for autovacuum.
>
> Access to GTT at replica:
> 1. Access is prohibited (as for original temp tables). No changes at all.
> 2. Tuples of temp tables are marked with forzen XID. Minimal changes,
> rollbacks are not possible.
> 3. Providing special XIDs for GTT at replica. No changes in CLOG are
> required, but special MVCC visibility rules are used for GTT. Current
> limitation: number of transactions accessing GTT at replica is limited by
> 2^32
> and bitmap of correspondent size has to be maintained (tuples of GTT are
> not proceeded by vacuum and not frozen, so XID horizon never moved).
>
> So except the limitation mentioned above (which I do not consider as
> critical) there is only one problem which was not addressed: maintaining
> statistics for GTT.
> If all of the following conditions are true:
>
> 1) GTT are used in joins
> 2) There are indexes defined for GTT
> 3) Size and histogram of GTT in different backends can significantly vary.
> 4) ANALYZE was explicitly called for GTT
>
> then query execution plan built in one backend will be also used for other
> backends where it can be inefficient.
> I also do not consider this problem as "show stopper" for adding GTT to
> Postgres.
>
The last issue is show stopper in my mind. It really depends on usage.
There are situation where shared statistics are ok (and maybe good
solution), and other situation, where shared statistics are just unusable.
Regards
Pavel
> 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
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-10-15 09:49 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 0 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2019-10-15 09:49 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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 https://www.postgresql.org/message-id/73954ab7-44d3-b37b-81a3-69bdcbb446f7%40postgrespro.ru <https://www.postgresql.org/message-id/73954ab7-44d3-b37b-81a3-69bdcbb446f7%40postgrespro.ru;
>> (5) A recent implementation and design from Konstantin Knizhnik covered many functions of GTT: https://www.postgresql.org/message-id/attachment/103265/global_private_temp-1.patch <https://www.postgresql.org/message-id/attachment/103265/global_private_temp-1.patch;
>>
>> However, as pointed by Konstantin himself, the implementation still needs functions related to CLOG, vacuum, and MVCC visibility.
>>
>
> Just to clarify.
> I have now proposed several different solutions for GTT:
>
> Shared vs. private buffers for GTT:
> 1. Private buffers. This is least invasive patch, requiring no changes in relfilenodes.
> 2. Shared buffers. Requires changing relfilenode but supports parallel query execution for GTT.
>
> This is important argument for using share buffers. Maybe the best is mix of both - store files in temporal tablespace, but using share buffers. More, it can be accessible for autovacuum.
>
> Access to GTT at replica:
> 1. Access is prohibited (as for original temp tables). No changes at all.
> 2. Tuples of temp tables are marked with forzen XID. Minimal changes, rollbacks are not possible.
> 3. Providing special XIDs for GTT at replica. No changes in CLOG are required, but special MVCC visibility rules are used for GTT. Current limitation: number of transactions accessing GTT at replica is limited by 2^32
> and bitmap of correspondent size has to be maintained (tuples of GTT are not proceeded by vacuum and not frozen, so XID horizon never moved).
>
> So except the limitation mentioned above (which I do not consider as critical) there is only one problem which was not addressed: maintaining statistics for GTT.
> If all of the following conditions are true:
>
> 1) GTT are used in joins
> 2) There are indexes defined for GTT
> 3) Size and histogram of GTT in different backends can significantly vary.
> 4) ANALYZE was explicitly called for GTT
>
> then query execution plan built in one backend will be also used for other backends where it can be inefficient.
> I also do not consider this problem as "show stopper" for adding GTT to Postgres.
>
> The last issue is show stopper in my mind. It really depends on usage. There are situation where shared statistics are ok (and maybe good solution), and other situation, where shared statistics are just unusable.
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.
> 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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-10-17 10:18 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2 siblings, 0 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2019-10-17 10:18 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: pgsql-hackers; Cai, Le <le.cai@alibaba-inc.com>
> 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 https://www.postgresql.org/message-id/73954ab7-44d3-b37b-81a3-69bdcbb446f7%40postgrespro.ru <https://www.postgresql.org/message-id/73954ab7-44d3-b37b-81a3-69bdcbb446f7%40postgrespro.ru;
>> (5) A recent implementation and design from Konstantin Knizhnik covered many functions of GTT: https://www.postgresql.org/message-id/attachment/103265/global_private_temp-1.patch <https://www.postgresql.org/message-id/attachment/103265/global_private_temp-1.patch;
>>
>> However, as pointed by Konstantin himself, the implementation still needs functions related to CLOG, vacuum, and MVCC visibility.
>>
>
> Just to clarify.
> I have now proposed several different solutions for GTT:
>
> Shared vs. private buffers for GTT:
> 1. Private buffers. This is least invasive patch, requiring no changes in relfilenodes.
> 2. Shared buffers. Requires changing relfilenode but supports parallel query execution for GTT.
>
> Access to GTT at replica:
> 1. Access is prohibited (as for original temp tables). No changes at all.
> 2. Tuples of temp tables are marked with forzen XID. Minimal changes, rollbacks are not possible.
> 3. Providing special XIDs for GTT at replica. No changes in CLOG are required, but special MVCC visibility rules are used for GTT. Current limitation: number of transactions accessing GTT at replica is limited by 2^32
> and bitmap of correspondent size has to be maintained (tuples of GTT are not proceeded by vacuum and not frozen, so XID horizon never moved).
>
> So except the limitation mentioned above (which I do not consider as critical) there is only one problem which was not addressed: maintaining statistics for GTT.
> If all of the following conditions are true:
>
> 1) GTT are used in joins
> 2) There are indexes defined for GTT
> 3) Size and histogram of GTT in different backends can significantly vary.
> 4) ANALYZE was explicitly called for GTT
>
> then query execution plan built in one backend will be also used for other backends where it can be inefficient.
> I also do not consider this problem as "show stopper" for adding GTT to Postgres.
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.
>
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com <http://www.postgrespro.com/;
> The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-10-25 15:01 Robert Haas <robertmhaas@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2 siblings, 2 replies; 525+ messages in thread
From: Robert Haas @ 2019-10-25 15:01 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On Fri, Oct 11, 2019 at 9:50 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:
> Just to clarify.
> I have now proposed several different solutions for GTT:
>
> Shared vs. private buffers for GTT:
> 1. Private buffers. This is least invasive patch, requiring no changes in relfilenodes.
> 2. Shared buffers. Requires changing relfilenode but supports parallel query execution for GTT.
I vote for #1. I think parallel query for temp objects may be a
desirable feature, but I don't think it should be the job of a patch
implementing GTTs to make it happen. In fact, I think it would be an
actively bad idea, because I suspect that if we do eventually support
temp relations for parallel query, we're going to want a solution that
is shared between regular temp tables and global temp tables, not
separate solutions for each.
> Access to GTT at replica:
> 1. Access is prohibited (as for original temp tables). No changes at all.
> 2. Tuples of temp tables are marked with forzen XID. Minimal changes, rollbacks are not possible.
> 3. Providing special XIDs for GTT at replica. No changes in CLOG are required, but special MVCC visibility rules are used for GTT. Current limitation: number of transactions accessing GTT at replica is limited by 2^32
> and bitmap of correspondent size has to be maintained (tuples of GTT are not proceeded by vacuum and not frozen, so XID horizon never moved).
I again vote for #1. A GTT is defined to allow data to be visible only
within one session -- so what does it even mean for the data to be
accessible on a replica?
> So except the limitation mentioned above (which I do not consider as critical) there is only one problem which was not addressed: maintaining statistics for GTT.
> If all of the following conditions are true:
>
> 1) GTT are used in joins
> 2) There are indexes defined for GTT
> 3) Size and histogram of GTT in different backends can significantly vary.
> 4) ANALYZE was explicitly called for GTT
>
> then query execution plan built in one backend will be also used for other backends where it can be inefficient.
> I also do not consider this problem as "show stopper" for adding GTT to Postgres.
I think that's *definitely* a show stopper.
> I still do not understand the opinion of community which functionality of GTT is considered to be most important.
> But the patch with local buffers and no replica support is small enough to become good starting point.
Well, it seems we now have two patches for this feature. I guess we
need to figure out which one is better, and whether it's possible for
the two efforts to be merged, rather than having two different teams
hacking on separate code bases.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-10-25 15:13 Pavel Stehule <pavel.stehule@gmail.com>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2019-10-25 15:13 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
pá 25. 10. 2019 v 17:01 odesílatel Robert Haas <robertmhaas@gmail.com>
napsal:
> On Fri, Oct 11, 2019 at 9:50 AM Konstantin Knizhnik
> <k.knizhnik@postgrespro.ru> wrote:
> > Just to clarify.
> > I have now proposed several different solutions for GTT:
> >
> > Shared vs. private buffers for GTT:
> > 1. Private buffers. This is least invasive patch, requiring no changes
> in relfilenodes.
> > 2. Shared buffers. Requires changing relfilenode but supports parallel
> query execution for GTT.
>
> I vote for #1. I think parallel query for temp objects may be a
> desirable feature, but I don't think it should be the job of a patch
> implementing GTTs to make it happen. In fact, I think it would be an
> actively bad idea, because I suspect that if we do eventually support
> temp relations for parallel query, we're going to want a solution that
> is shared between regular temp tables and global temp tables, not
> separate solutions for each.
>
> > Access to GTT at replica:
> > 1. Access is prohibited (as for original temp tables). No changes at all.
> > 2. Tuples of temp tables are marked with forzen XID. Minimal changes,
> rollbacks are not possible.
> > 3. Providing special XIDs for GTT at replica. No changes in CLOG are
> required, but special MVCC visibility rules are used for GTT. Current
> limitation: number of transactions accessing GTT at replica is limited by
> 2^32
> > and bitmap of correspondent size has to be maintained (tuples of GTT are
> not proceeded by vacuum and not frozen, so XID horizon never moved).
>
> I again vote for #1. A GTT is defined to allow data to be visible only
> within one session -- so what does it even mean for the data to be
> accessible on a replica?
>
why not? there are lot of sessions on replica servers. One usage of temp
tables is fixing estimation errors. You can create temp table with partial
query result, run ANALYZE and evaluate other steps. Now this case is not
possible on replica servers.
One motivation for GTT is decreasing port costs from Oracle. But other
motivations, like do more complex calculations on replica are valid and
valuable.
> > So except the limitation mentioned above (which I do not consider as
> critical) there is only one problem which was not addressed: maintaining
> statistics for GTT.
> > If all of the following conditions are true:
> >
> > 1) GTT are used in joins
> > 2) There are indexes defined for GTT
> > 3) Size and histogram of GTT in different backends can significantly
> vary.
> > 4) ANALYZE was explicitly called for GTT
> >
> > then query execution plan built in one backend will be also used for
> other backends where it can be inefficient.
> > I also do not consider this problem as "show stopper" for adding GTT to
> Postgres.
>
> I think that's *definitely* a show stopper.
>
> > I still do not understand the opinion of community which functionality
> of GTT is considered to be most important.
> > But the patch with local buffers and no replica support is small enough
> to become good starting point.
>
> Well, it seems we now have two patches for this feature. I guess we
> need to figure out which one is better, and whether it's possible for
> the two efforts to be merged, rather than having two different teams
> hacking on separate code bases.
>
> --
> Robert Haas
> EnterpriseDB: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-10-25 16:22 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 3 replies; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-10-25 16:22 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On 25.10.2019 18:01, Robert Haas wrote:
> On Fri, Oct 11, 2019 at 9:50 AM Konstantin Knizhnik
> <k.knizhnik@postgrespro.ru> wrote:
>> Just to clarify.
>> I have now proposed several different solutions for GTT:
>>
>> Shared vs. private buffers for GTT:
>> 1. Private buffers. This is least invasive patch, requiring no changes in relfilenodes.
>> 2. Shared buffers. Requires changing relfilenode but supports parallel query execution for GTT.
> I vote for #1. I think parallel query for temp objects may be a
> desirable feature, but I don't think it should be the job of a patch
> implementing GTTs to make it happen. In fact, I think it would be an
> actively bad idea, because I suspect that if we do eventually support
> temp relations for parallel query, we're going to want a solution that
> is shared between regular temp tables and global temp tables, not
> separate solutions for each.
Sorry, may be I do not not understand you.
It seems to me that there is only one thing preventing usage of
temporary tables in parallel plans: private buffers.
If global temporary tables are accessed as normal tables though shared
buffers then them can be used in parallel queries
and no extra support is required for it.
At least I have checked that parallel queries are correctly worked for
my implementation of GTT with shared buffers.
So I do not understand about which "separate solutions" you are talking
about.
I can agree that private buffers may be good starting point for GTT
implementation, because it is less invasive and GTT access speed is
exactly the same as of normal temp tables.
But I do not understand your argument why it is "actively bad idea".
>> Access to GTT at replica:
>> 1. Access is prohibited (as for original temp tables). No changes at all.
>> 2. Tuples of temp tables are marked with forzen XID. Minimal changes, rollbacks are not possible.
>> 3. Providing special XIDs for GTT at replica. No changes in CLOG are required, but special MVCC visibility rules are used for GTT. Current limitation: number of transactions accessing GTT at replica is limited by 2^32
>> and bitmap of correspondent size has to be maintained (tuples of GTT are not proceeded by vacuum and not frozen, so XID horizon never moved).
> I again vote for #1. A GTT is defined to allow data to be visible only
> within one session -- so what does it even mean for the data to be
> accessible on a replica?
There are sessions at replica (in case of hot standby), aren't there?
>
>> So except the limitation mentioned above (which I do not consider as critical) there is only one problem which was not addressed: maintaining statistics for GTT.
>> If all of the following conditions are true:
>>
>> 1) GTT are used in joins
>> 2) There are indexes defined for GTT
>> 3) Size and histogram of GTT in different backends can significantly vary.
>> 4) ANALYZE was explicitly called for GTT
>>
>> then query execution plan built in one backend will be also used for other backends where it can be inefficient.
>> I also do not consider this problem as "show stopper" for adding GTT to Postgres.
> I think that's *definitely* a show stopper.
Well, if both you and Pavel think that it is really "show stopper", then
this problem really has to be addressed.
I slightly confused about this opinion, because Pavel has told me
himself that 99% of users never create indexes for temp tables
or run "analyze" for them. And without it, this problem is not a problem
at all.
>> I still do not understand the opinion of community which functionality of GTT is considered to be most important.
>> But the patch with local buffers and no replica support is small enough to become good starting point.
> Well, it seems we now have two patches for this feature. I guess we
> need to figure out which one is better, and whether it's possible for
> the two efforts to be merged, rather than having two different teams
> hacking on separate code bases.
I am open for cooperations.
Source code of all my patches is available.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-10-25 17:00 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2019-10-25 17:00 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> >
> >> So except the limitation mentioned above (which I do not consider as
> critical) there is only one problem which was not addressed: maintaining
> statistics for GTT.
> >> If all of the following conditions are true:
> >>
> >> 1) GTT are used in joins
> >> 2) There are indexes defined for GTT
> >> 3) Size and histogram of GTT in different backends can significantly
> vary.
> >> 4) ANALYZE was explicitly called for GTT
> >>
> >> then query execution plan built in one backend will be also used for
> other backends where it can be inefficient.
> >> I also do not consider this problem as "show stopper" for adding GTT to
> Postgres.
> > I think that's *definitely* a show stopper.
> Well, if both you and Pavel think that it is really "show stopper", then
> this problem really has to be addressed.
> I slightly confused about this opinion, because Pavel has told me
> himself that 99% of users never create indexes for temp tables
> or run "analyze" for them. And without it, this problem is not a problem
> at all.
>
>
Users doesn't do ANALYZE on temp tables in 99%. It's true. But second fact
is so users has lot of problems. It's very similar to wrong statistics on
persistent tables. When data are small, then it is not problem for users,
although from my perspective it's not optimal. When data are not small,
then the problem can be brutal. Temporary tables are not a exception. And
users and developers are people - we know only about fatal problems. There
are lot of unoptimized queries, but because the problem is not fatal, then
it is not reason for report it. And lot of people has not any idea how fast
the databases can be. The knowledges of users and app developers are sad
book.
Pavel
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-10-28 07:15 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2 siblings, 0 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2019-10-28 07:15 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 2019年10月26日 上午12:22,Konstantin Knizhnik <k.knizhnik@postgrespro.ru> 写道:
>
>
>
> On 25.10.2019 18:01, Robert Haas wrote:
>> On Fri, Oct 11, 2019 at 9:50 AM Konstantin Knizhnik
>> <k.knizhnik@postgrespro.ru> wrote:
>>> Just to clarify.
>>> I have now proposed several different solutions for GTT:
>>>
>>> Shared vs. private buffers for GTT:
>>> 1. Private buffers. This is least invasive patch, requiring no changes in relfilenodes.
>>> 2. Shared buffers. Requires changing relfilenode but supports parallel query execution for GTT.
>> I vote for #1. I think parallel query for temp objects may be a
>> desirable feature, but I don't think it should be the job of a patch
>> implementing GTTs to make it happen. In fact, I think it would be an
>> actively bad idea, because I suspect that if we do eventually support
>> temp relations for parallel query, we're going to want a solution that
>> is shared between regular temp tables and global temp tables, not
>> separate solutions for each.
>
> Sorry, may be I do not not understand you.
> It seems to me that there is only one thing preventing usage of temporary tables in parallel plans: private buffers.
> If global temporary tables are accessed as normal tables though shared buffers then them can be used in parallel queries
> and no extra support is required for it.
> At least I have checked that parallel queries are correctly worked for my implementation of GTT with shared buffers.
> So I do not understand about which "separate solutions" you are talking about.
>
> I can agree that private buffers may be good starting point for GTT implementation, because it is less invasive and GTT access speed is exactly the same as of normal temp tables.
> But I do not understand your argument why it is "actively bad idea".
>
>>> Access to GTT at replica:
>>> 1. Access is prohibited (as for original temp tables). No changes at all.
>>> 2. Tuples of temp tables are marked with forzen XID. Minimal changes, rollbacks are not possible.
>>> 3. Providing special XIDs for GTT at replica. No changes in CLOG are required, but special MVCC visibility rules are used for GTT. Current limitation: number of transactions accessing GTT at replica is limited by 2^32
>>> and bitmap of correspondent size has to be maintained (tuples of GTT are not proceeded by vacuum and not frozen, so XID horizon never moved).
>> I again vote for #1. A GTT is defined to allow data to be visible only
>> within one session -- so what does it even mean for the data to be
>> accessible on a replica?
>
> There are sessions at replica (in case of hot standby), aren't there?
>
>>
>>> So except the limitation mentioned above (which I do not consider as critical) there is only one problem which was not addressed: maintaining statistics for GTT.
>>> If all of the following conditions are true:
>>>
>>> 1) GTT are used in joins
>>> 2) There are indexes defined for GTT
>>> 3) Size and histogram of GTT in different backends can significantly vary.
>>> 4) ANALYZE was explicitly called for GTT
>>>
>>> then query execution plan built in one backend will be also used for other backends where it can be inefficient.
>>> I also do not consider this problem as "show stopper" for adding GTT to Postgres.
>> I think that's *definitely* a show stopper.
> Well, if both you and Pavel think that it is really "show stopper", then this problem really has to be addressed.
> I slightly confused about this opinion, because Pavel has told me himself that 99% of users never create indexes for temp tables
> or run "analyze" for them. And without it, this problem is not a problem at all.
>
>>> I still do not understand the opinion of community which functionality of GTT is considered to be most important.
>>> But the patch with local buffers and no replica support is small enough to become good starting point.
>> Well, it seems we now have two patches for this feature. I guess we
>> need to figure out which one is better, and whether it's possible for
>> the two efforts to be merged, rather than having two different teams
>> hacking on separate code bases.
>
> I am open for cooperations.
> Source code of all my patches is available.
We are also willing to cooperate to complete this feature.
Let me prepare the code(merge code to pg12) and up to community, then see how we work together.
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-10-28 12:07 Robert Haas <robertmhaas@gmail.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Robert Haas @ 2019-10-28 12:07 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-10-28 12:13 Robert Haas <robertmhaas@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2 siblings, 1 reply; 525+ messages in thread
From: Robert Haas @ 2019-10-28 12:13 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-10-28 13:37 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-10-28 13:37 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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:
[text/x-patch] replica-gtt-xact.diff (18.2K, ../../8dc812ea-af3f-7a9d-ae9a-d89c25ca89ca@postgrespro.ru/2-replica-gtt-xact.diff)
download | inline diff:
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();
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-10-28 13:48 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-10-28 13:48 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On 28.10.2019 15:13, Robert Haas wrote:
> On Fri, Oct 25, 2019 at 12:22 PM Konstantin Knizhnik
> <k.knizhnik@postgrespro.ru> wrote:
>> On 25.10.2019 18:01, Robert Haas wrote:
>>> On Fri, Oct 11, 2019 at 9:50 AM Konstantin Knizhnik
>>> <k.knizhnik@postgrespro.ru> wrote:
>>>> Just to clarify.
>>>> I have now proposed several different solutions for GTT:
>>>>
>>>> Shared vs. private buffers for GTT:
>>>> 1. Private buffers. This is least invasive patch, requiring no changes in relfilenodes.
>>>> 2. Shared buffers. Requires changing relfilenode but supports parallel query execution for GTT.
>>> I vote for #1. I think parallel query for temp objects may be a
>>> desirable feature, but I don't think it should be the job of a patch
>>> implementing GTTs to make it happen. In fact, I think it would be an
>>> actively bad idea, because I suspect that if we do eventually support
>>> temp relations for parallel query, we're going to want a solution that
>>> is shared between regular temp tables and global temp tables, not
>>> separate solutions for each.
>> Sorry, may be I do not not understand you.
>> It seems to me that there is only one thing preventing usage of
>> temporary tables in parallel plans: private buffers.
>> If global temporary tables are accessed as normal tables though shared
>> buffers then them can be used in parallel queries
>> and no extra support is required for it.
>> At least I have checked that parallel queries are correctly worked for
>> my implementation of GTT with shared buffers.
>> So I do not understand about which "separate solutions" you are talking
>> about.
>>
>> I can agree that private buffers may be good starting point for GTT
>> implementation, because it is less invasive and GTT access speed is
>> exactly the same as of normal temp tables.
>> But I do not understand your argument why it is "actively bad idea".
> Well, it sounds like you're talking about ending up in a situation
> where local temporary tables are still in private buffers, but global
> temporary table data is in shared buffers. I think that would be
> inconsistent. And it would mean that when somebody wanted to make
> local temporary tables accessible in parallel query, they'd have to
> write a patch for that. In other words, I don't support dividing the
> patches like this:
>
> Patch #1: Support global temporary tables + allow global temporary
> tables to used by parallel query
> Patch #2: Allow local temporary tables to be used by parallel query
>
> I support dividing them like this:
>
> Patch #1: Support global temporary tables
> Patch #2: Allow (all kinds of) temporary tables to be used by parallel query
>
> The second division looks a lot cleaner to me, although as always I
> might be missing something.
>
Logically it may be good decision. But piratically support of parallel
access to GTT requires just accessing their data through shared buffer.
But in case of local temp tables we need also need to some how share
table's metadata between parallel workers. It seems to be much more
complicated if ever possible.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-10-28 16:29 Robert Haas <robertmhaas@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 0 replies; 525+ messages in thread
From: Robert Haas @ 2019-10-28 16:29 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-10-28 16:40 Robert Haas <robertmhaas@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 2 replies; 525+ messages in thread
From: Robert Haas @ 2019-10-28 16:40 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-10-29 08:04 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 0 replies; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-10-29 08:04 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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:
[text/x-patch] global_private_temp-3.patch (59.3K, ../../5190f923-19f3-5fe3-09ed-dec12fc91aaf@postgrespro.ru/2-global_private_temp-3.patch)
download | inline diff:
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;
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-01 15:15 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 2 replies; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-11-01 15:15 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On 25.10.2019 20:00, Pavel Stehule wrote:
>
> >
> >> So except the limitation mentioned above (which I do not
> consider as critical) there is only one problem which was not
> addressed: maintaining statistics for GTT.
> >> If all of the following conditions are true:
> >>
> >> 1) GTT are used in joins
> >> 2) There are indexes defined for GTT
> >> 3) Size and histogram of GTT in different backends can
> significantly vary.
> >> 4) ANALYZE was explicitly called for GTT
> >>
> >> then query execution plan built in one backend will be also
> used for other backends where it can be inefficient.
> >> I also do not consider this problem as "show stopper" for
> adding GTT to Postgres.
> > I think that's *definitely* a show stopper.
> Well, if both you and Pavel think that it is really "show
> stopper", then
> this problem really has to be addressed.
> I slightly confused about this opinion, because Pavel has told me
> himself that 99% of users never create indexes for temp tables
> or run "analyze" for them. And without it, this problem is not a
> problem
> at all.
>
>
> Users doesn't do ANALYZE on temp tables in 99%. It's true. But second
> fact is so users has lot of problems. It's very similar to wrong
> statistics on persistent tables. When data are small, then it is not
> problem for users, although from my perspective it's not optimal. When
> data are not small, then the problem can be brutal. Temporary tables
> are not a exception. And users and developers are people - we know
> only about fatal problems. There are lot of unoptimized queries, but
> because the problem is not fatal, then it is not reason for report it.
> And lot of people has not any idea how fast the databases can be. The
> knowledges of users and app developers are sad book.
>
> Pavel
It seems to me that I have found quite elegant solution for per-backend
statistic for GTT: I just inserting it in backend's catalog cache, but
not in pg_statistic table itself.
To do it I have to add InsertSysCache/InsertCatCache functions which
insert pinned entry in the correspondent cache.
I wonder if there are some pitfalls of such approach?
New patch for GTT is attached.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
[text/x-patch] global_private_temp-4.patch (67.7K, ../../80cf30c2-ebd5-a568-9f62-a970e4d91326@postgrespro.ru/3-global_private_temp-4.patch)
download | inline diff:
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;
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-01 15:26 Robert Haas <robertmhaas@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
1 sibling, 1 reply; 525+ messages in thread
From: Robert Haas @ 2019-11-01 15:26 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-01 16:09 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-11-01 16:09 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-02 05:30 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 2 replies; 525+ messages in thread
From: Pavel Stehule @ 2019-11-02 05:30 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-02 07:19 Julien Rouhaud <rjuju123@gmail.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
1 sibling, 2 replies; 525+ messages in thread
From: Julien Rouhaud @ 2019-11-02 07:19 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On Sat, Nov 2, 2019 at 6:31 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
>
> pá 1. 11. 2019 v 17:09 odesílatel Konstantin Knizhnik <k.knizhnik@postgrespro.ru> napsal:
>>
>> On 01.11.2019 18:26, Robert Haas wrote:
>> > On Fri, Nov 1, 2019 at 11:15 AM Konstantin Knizhnik
>> > <k.knizhnik@postgrespro.ru> wrote:
>> >> It seems to me that I have found quite elegant solution for per-backend statistic for GTT: I just inserting it in backend's catalog cache, but not in pg_statistic table itself.
>> >> To do it I have to add InsertSysCache/InsertCatCache functions which insert pinned entry in the correspondent cache.
>> >> I wonder if there are some pitfalls of such approach?
>> > That sounds pretty hackish. You'd have to be very careful, for
>> > example, that if the tables were dropped or re-analyzed, all of the
>> > old entries got removed --
>>
>> I have checked it:
>> - when table is reanalyzed, then cache entries are replaced.
>> - when table is dropped, then cache entries are removed.
>>
>> > and then it would still fail if any code
>> > tried to access the statistics directly from the table, rather than
>> > via the caches. My assumption is that the statistics ought to be
>> > stored in some backend-private data structure designed for that
>> > purpose, and that the code that needs the data should be taught to
>> > look for it there when the table is a GTT.
>>
>> Yes, if you do "select * from pg_statistic" then you will not see
>> statistic for GTT in this case.
>> But I do not think that it is so critical. I do not believe that anybody
>> is trying to manually interpret values in this table.
>> And optimizer is retrieving statistic through sys-cache mechanism and so
>> is able to build correct plan in this case.
>
>
> Years ago, when I though about it, I wrote patch with similar design. It's working, but surely it's ugly.
>
> I have another idea. Can be pg_statistics view instead a table?
>
> Some like
>
> SELECT * FROM pg_catalog.pg_statistics_rel
> UNION ALL
> SELECT * FROM pg_catalog.pg_statistics_gtt();
>
> Internally - when stat cache is filled, then there can be used pg_statistics_rel and pg_statistics_gtt() directly. What I remember, there was not possibility to work with queries, only with just relations.
It'd be a loss if you lose the ability to see the statistics, as there
are valid use cases where you need to see the stats, eg. understanding
why you don't get the plan you wanted. There's also at least one
extension [1] that allows you to backup and use restored statistics,
so there are definitely people interested in it.
[1]: https://github.com/ossc-db/pg_dbms_stats
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-02 07:23 Pavel Stehule <pavel.stehule@gmail.com>
parent: Julien Rouhaud <rjuju123@gmail.com>
1 sibling, 2 replies; 525+ messages in thread
From: Pavel Stehule @ 2019-11-02 07:23 UTC (permalink / raw)
To: Julien Rouhaud <rjuju123@gmail.com>; +Cc: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
so 2. 11. 2019 v 8:18 odesílatel Julien Rouhaud <rjuju123@gmail.com> napsal:
> On Sat, Nov 2, 2019 at 6:31 AM Pavel Stehule <pavel.stehule@gmail.com>
> wrote:
> >
> > pá 1. 11. 2019 v 17:09 odesílatel Konstantin Knizhnik <
> k.knizhnik@postgrespro.ru> napsal:
> >>
> >> On 01.11.2019 18:26, Robert Haas wrote:
> >> > On Fri, Nov 1, 2019 at 11:15 AM Konstantin Knizhnik
> >> > <k.knizhnik@postgrespro.ru> wrote:
> >> >> It seems to me that I have found quite elegant solution for
> per-backend statistic for GTT: I just inserting it in backend's catalog
> cache, but not in pg_statistic table itself.
> >> >> To do it I have to add InsertSysCache/InsertCatCache functions which
> insert pinned entry in the correspondent cache.
> >> >> I wonder if there are some pitfalls of such approach?
> >> > That sounds pretty hackish. You'd have to be very careful, for
> >> > example, that if the tables were dropped or re-analyzed, all of the
> >> > old entries got removed --
> >>
> >> I have checked it:
> >> - when table is reanalyzed, then cache entries are replaced.
> >> - when table is dropped, then cache entries are removed.
> >>
> >> > and then it would still fail if any code
> >> > tried to access the statistics directly from the table, rather than
> >> > via the caches. My assumption is that the statistics ought to be
> >> > stored in some backend-private data structure designed for that
> >> > purpose, and that the code that needs the data should be taught to
> >> > look for it there when the table is a GTT.
> >>
> >> Yes, if you do "select * from pg_statistic" then you will not see
> >> statistic for GTT in this case.
> >> But I do not think that it is so critical. I do not believe that anybody
> >> is trying to manually interpret values in this table.
> >> And optimizer is retrieving statistic through sys-cache mechanism and so
> >> is able to build correct plan in this case.
> >
> >
> > Years ago, when I though about it, I wrote patch with similar design.
> It's working, but surely it's ugly.
> >
> > I have another idea. Can be pg_statistics view instead a table?
> >
> > Some like
> >
> > SELECT * FROM pg_catalog.pg_statistics_rel
> > UNION ALL
> > SELECT * FROM pg_catalog.pg_statistics_gtt();
> >
> > Internally - when stat cache is filled, then there can be used
> pg_statistics_rel and pg_statistics_gtt() directly. What I remember, there
> was not possibility to work with queries, only with just relations.
>
> It'd be a loss if you lose the ability to see the statistics, as there
> are valid use cases where you need to see the stats, eg. understanding
> why you don't get the plan you wanted. There's also at least one
> extension [1] that allows you to backup and use restored statistics,
> so there are definitely people interested in it.
>
> [1]: https://github.com/ossc-db/pg_dbms_stats
I don't think - the extensions can use UNION and the content will be same
as caches used by planner.
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-02 07:24 Pavel Stehule <pavel.stehule@gmail.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
1 sibling, 0 replies; 525+ messages in thread
From: Pavel Stehule @ 2019-11-02 07:24 UTC (permalink / raw)
To: Julien Rouhaud <rjuju123@gmail.com>; +Cc: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
so 2. 11. 2019 v 8:23 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:
>
>
> so 2. 11. 2019 v 8:18 odesílatel Julien Rouhaud <rjuju123@gmail.com>
> napsal:
>
>> On Sat, Nov 2, 2019 at 6:31 AM Pavel Stehule <pavel.stehule@gmail.com>
>> wrote:
>> >
>> > pá 1. 11. 2019 v 17:09 odesílatel Konstantin Knizhnik <
>> k.knizhnik@postgrespro.ru> napsal:
>> >>
>> >> On 01.11.2019 18:26, Robert Haas wrote:
>> >> > On Fri, Nov 1, 2019 at 11:15 AM Konstantin Knizhnik
>> >> > <k.knizhnik@postgrespro.ru> wrote:
>> >> >> It seems to me that I have found quite elegant solution for
>> per-backend statistic for GTT: I just inserting it in backend's catalog
>> cache, but not in pg_statistic table itself.
>> >> >> To do it I have to add InsertSysCache/InsertCatCache functions
>> which insert pinned entry in the correspondent cache.
>> >> >> I wonder if there are some pitfalls of such approach?
>> >> > That sounds pretty hackish. You'd have to be very careful, for
>> >> > example, that if the tables were dropped or re-analyzed, all of the
>> >> > old entries got removed --
>> >>
>> >> I have checked it:
>> >> - when table is reanalyzed, then cache entries are replaced.
>> >> - when table is dropped, then cache entries are removed.
>> >>
>> >> > and then it would still fail if any code
>> >> > tried to access the statistics directly from the table, rather than
>> >> > via the caches. My assumption is that the statistics ought to be
>> >> > stored in some backend-private data structure designed for that
>> >> > purpose, and that the code that needs the data should be taught to
>> >> > look for it there when the table is a GTT.
>> >>
>> >> Yes, if you do "select * from pg_statistic" then you will not see
>> >> statistic for GTT in this case.
>> >> But I do not think that it is so critical. I do not believe that
>> anybody
>> >> is trying to manually interpret values in this table.
>> >> And optimizer is retrieving statistic through sys-cache mechanism and
>> so
>> >> is able to build correct plan in this case.
>> >
>> >
>> > Years ago, when I though about it, I wrote patch with similar design.
>> It's working, but surely it's ugly.
>> >
>> > I have another idea. Can be pg_statistics view instead a table?
>> >
>> > Some like
>> >
>> > SELECT * FROM pg_catalog.pg_statistics_rel
>> > UNION ALL
>> > SELECT * FROM pg_catalog.pg_statistics_gtt();
>> >
>> > Internally - when stat cache is filled, then there can be used
>> pg_statistics_rel and pg_statistics_gtt() directly. What I remember, there
>> was not possibility to work with queries, only with just relations.
>>
>> It'd be a loss if you lose the ability to see the statistics, as there
>> are valid use cases where you need to see the stats, eg. understanding
>> why you don't get the plan you wanted. There's also at least one
>> extension [1] that allows you to backup and use restored statistics,
>> so there are definitely people interested in it.
>>
>> [1]: https://github.com/ossc-db/pg_dbms_stats
>
>
> I don't think - the extensions can use UNION and the content will be same
> as caches used by planner.
>
sure, if some one try to modify directly system tables, then it should be
fixed.
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-02 08:56 Julien Rouhaud <rjuju123@gmail.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
1 sibling, 0 replies; 525+ messages in thread
From: Julien Rouhaud @ 2019-11-02 08:56 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On Sat, Nov 2, 2019 at 8:23 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
>
> so 2. 11. 2019 v 8:18 odesílatel Julien Rouhaud <rjuju123@gmail.com> napsal:
>>
>> On Sat, Nov 2, 2019 at 6:31 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
>> >
>> > pá 1. 11. 2019 v 17:09 odesílatel Konstantin Knizhnik <k.knizhnik@postgrespro.ru> napsal:
>> >>
>> >> On 01.11.2019 18:26, Robert Haas wrote:
>> >> > On Fri, Nov 1, 2019 at 11:15 AM Konstantin Knizhnik
>> >> > <k.knizhnik@postgrespro.ru> wrote:
>> >> >> It seems to me that I have found quite elegant solution for per-backend statistic for GTT: I just inserting it in backend's catalog cache, but not in pg_statistic table itself.
>> >> >> To do it I have to add InsertSysCache/InsertCatCache functions which insert pinned entry in the correspondent cache.
>> >> >> I wonder if there are some pitfalls of such approach?
>> >> > That sounds pretty hackish. You'd have to be very careful, for
>> >> > example, that if the tables were dropped or re-analyzed, all of the
>> >> > old entries got removed --
>> >>
>> >> I have checked it:
>> >> - when table is reanalyzed, then cache entries are replaced.
>> >> - when table is dropped, then cache entries are removed.
>> >>
>> >> > and then it would still fail if any code
>> >> > tried to access the statistics directly from the table, rather than
>> >> > via the caches. My assumption is that the statistics ought to be
>> >> > stored in some backend-private data structure designed for that
>> >> > purpose, and that the code that needs the data should be taught to
>> >> > look for it there when the table is a GTT.
>> >>
>> >> Yes, if you do "select * from pg_statistic" then you will not see
>> >> statistic for GTT in this case.
>> >> But I do not think that it is so critical. I do not believe that anybody
>> >> is trying to manually interpret values in this table.
>> >> And optimizer is retrieving statistic through sys-cache mechanism and so
>> >> is able to build correct plan in this case.
>> >
>> >
>> > Years ago, when I though about it, I wrote patch with similar design. It's working, but surely it's ugly.
>> >
>> > I have another idea. Can be pg_statistics view instead a table?
>> >
>> > Some like
>> >
>> > SELECT * FROM pg_catalog.pg_statistics_rel
>> > UNION ALL
>> > SELECT * FROM pg_catalog.pg_statistics_gtt();
>> >
>> > Internally - when stat cache is filled, then there can be used pg_statistics_rel and pg_statistics_gtt() directly. What I remember, there was not possibility to work with queries, only with just relations.
>>
>> It'd be a loss if you lose the ability to see the statistics, as there
>> are valid use cases where you need to see the stats, eg. understanding
>> why you don't get the plan you wanted. There's also at least one
>> extension [1] that allows you to backup and use restored statistics,
>> so there are definitely people interested in it.
>>
>> [1]: https://github.com/ossc-db/pg_dbms_stats
>
>
> I don't think - the extensions can use UNION and the content will be same as caches used by planner.
Yes, I agree that changing pg_statistics to be a view as you showed
would fix the problem. I was answering Konstantin's point:
>> >> But I do not think that it is so critical. I do not believe that anybody
>> >> is trying to manually interpret values in this table.
>> >> And optimizer is retrieving statistic through sys-cache mechanism and so
>> >> is able to build correct plan in this case.
which is IMHO a wrong assumption.
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-02 15:09 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Julien Rouhaud <rjuju123@gmail.com>
1 sibling, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-11-02 15:09 UTC (permalink / raw)
To: Julien Rouhaud <rjuju123@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On 02.11.2019 10:19, Julien Rouhaud wrote:
> On Sat, Nov 2, 2019 at 6:31 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
>> pá 1. 11. 2019 v 17:09 odesílatel Konstantin Knizhnik <k.knizhnik@postgrespro.ru> napsal:
>>> On 01.11.2019 18:26, Robert Haas wrote:
>>>> On Fri, Nov 1, 2019 at 11:15 AM Konstantin Knizhnik
>>>> <k.knizhnik@postgrespro.ru> wrote:
>>>>> It seems to me that I have found quite elegant solution for per-backend statistic for GTT: I just inserting it in backend's catalog cache, but not in pg_statistic table itself.
>>>>> To do it I have to add InsertSysCache/InsertCatCache functions which insert pinned entry in the correspondent cache.
>>>>> I wonder if there are some pitfalls of such approach?
>>>> That sounds pretty hackish. You'd have to be very careful, for
>>>> example, that if the tables were dropped or re-analyzed, all of the
>>>> old entries got removed --
>>> I have checked it:
>>> - when table is reanalyzed, then cache entries are replaced.
>>> - when table is dropped, then cache entries are removed.
>>>
>>>> and then it would still fail if any code
>>>> tried to access the statistics directly from the table, rather than
>>>> via the caches. My assumption is that the statistics ought to be
>>>> stored in some backend-private data structure designed for that
>>>> purpose, and that the code that needs the data should be taught to
>>>> look for it there when the table is a GTT.
>>> Yes, if you do "select * from pg_statistic" then you will not see
>>> statistic for GTT in this case.
>>> But I do not think that it is so critical. I do not believe that anybody
>>> is trying to manually interpret values in this table.
>>> And optimizer is retrieving statistic through sys-cache mechanism and so
>>> is able to build correct plan in this case.
>>
>> Years ago, when I though about it, I wrote patch with similar design. It's working, but surely it's ugly.
>>
>> I have another idea. Can be pg_statistics view instead a table?
>>
>> Some like
>>
>> SELECT * FROM pg_catalog.pg_statistics_rel
>> UNION ALL
>> SELECT * FROM pg_catalog.pg_statistics_gtt();
>>
>> Internally - when stat cache is filled, then there can be used pg_statistics_rel and pg_statistics_gtt() directly. What I remember, there was not possibility to work with queries, only with just relations.
> It'd be a loss if you lose the ability to see the statistics, as there
> are valid use cases where you need to see the stats, eg. understanding
> why you don't get the plan you wanted. There's also at least one
> extension [1] that allows you to backup and use restored statistics,
> so there are definitely people interested in it.
>
> [1]: https://github.com/ossc-db/pg_dbms_stats
It seems to have completely no sense to backup and restore statistic for
temporary tables which life time is limited to life time of backend,
doesn't it?
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-02 15:15 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Pavel Stehule <pavel.stehule@gmail.com>
1 sibling, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-11-02 15:15 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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.
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-02 15:30 Julien Rouhaud <rjuju123@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 0 replies; 525+ messages in thread
From: Julien Rouhaud @ 2019-11-02 15:30 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On Sat, Nov 2, 2019 at 4:09 PM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:
>
> On 02.11.2019 10:19, Julien Rouhaud wrote:
> > On Sat, Nov 2, 2019 at 6:31 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
> >> pá 1. 11. 2019 v 17:09 odesílatel Konstantin Knizhnik <k.knizhnik@postgrespro.ru> napsal:
> >>> On 01.11.2019 18:26, Robert Haas wrote:
> >>>> On Fri, Nov 1, 2019 at 11:15 AM Konstantin Knizhnik
> >>>> <k.knizhnik@postgrespro.ru> wrote:
> >>>>> It seems to me that I have found quite elegant solution for per-backend statistic for GTT: I just inserting it in backend's catalog cache, but not in pg_statistic table itself.
> >>>>> To do it I have to add InsertSysCache/InsertCatCache functions which insert pinned entry in the correspondent cache.
> >>>>> I wonder if there are some pitfalls of such approach?
> >>>> That sounds pretty hackish. You'd have to be very careful, for
> >>>> example, that if the tables were dropped or re-analyzed, all of the
> >>>> old entries got removed --
> >>> I have checked it:
> >>> - when table is reanalyzed, then cache entries are replaced.
> >>> - when table is dropped, then cache entries are removed.
> >>>
> >>>> and then it would still fail if any code
> >>>> tried to access the statistics directly from the table, rather than
> >>>> via the caches. My assumption is that the statistics ought to be
> >>>> stored in some backend-private data structure designed for that
> >>>> purpose, and that the code that needs the data should be taught to
> >>>> look for it there when the table is a GTT.
> >>> Yes, if you do "select * from pg_statistic" then you will not see
> >>> statistic for GTT in this case.
> >>> But I do not think that it is so critical. I do not believe that anybody
> >>> is trying to manually interpret values in this table.
> >>> And optimizer is retrieving statistic through sys-cache mechanism and so
> >>> is able to build correct plan in this case.
> >>
> >> Years ago, when I though about it, I wrote patch with similar design. It's working, but surely it's ugly.
> >>
> >> I have another idea. Can be pg_statistics view instead a table?
> >>
> >> Some like
> >>
> >> SELECT * FROM pg_catalog.pg_statistics_rel
> >> UNION ALL
> >> SELECT * FROM pg_catalog.pg_statistics_gtt();
> >>
> >> Internally - when stat cache is filled, then there can be used pg_statistics_rel and pg_statistics_gtt() directly. What I remember, there was not possibility to work with queries, only with just relations.
> > It'd be a loss if you lose the ability to see the statistics, as there
> > are valid use cases where you need to see the stats, eg. understanding
> > why you don't get the plan you wanted. There's also at least one
> > extension [1] that allows you to backup and use restored statistics,
> > so there are definitely people interested in it.
> >
> > [1]: https://github.com/ossc-db/pg_dbms_stats
> It seems to have completely no sense to backup and restore statistic for
> temporary tables which life time is limited to life time of backend,
> doesn't it?
In general yes I agree, but it doesn't if the goal is to understand
why even after an analyze on the temporary table your query is still
behaving poorly. It can be useful to allow reproduction or just give
someone else the statistics to see what's going on.
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-02 16:34 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 0 replies; 525+ messages in thread
From: Pavel Stehule @ 2019-11-02 16:34 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 张广舟(明虚) <guangzhou.zgz@alibaba-inc.com>; 赵殿奎 <diankui.zdk@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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.
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-06 13:24 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2019-11-06 13:24 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
> 2019年10月29日 上午12:40,Robert Haas <robertmhaas@gmail.com> 写道:
>
> 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
Attachments:
[application/octet-stream] global_temporary_table_v1.patch (136.5K, ../../0FC4E40B-4B4E-4B10-B2AA-F8E8E914F822@alibaba-inc.com/2-global_temporary_table_v1.patch)
download | inline diff:
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
=
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-06 16:08 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-11-06 16:08 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>; +Cc: pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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:
[text/x-patch] global_temporary_table_v1-pg13.patch (128.5K, ../../e6fb54c2-bc4f-b8f0-f189-52d43be510ef@postgrespro.ru/2-global_temporary_table_v1-pg13.patch)
download | inline diff:
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;
+
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-07 09:30 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 3 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2019-11-07 09:30 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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.
> But right now this check is not working correctly in any case - if you insert some data into the table, then
> you can not create index any more:
>
> postgres=# create global temp table gtt(x integer primary key, y integer);
> CREATE TABLE
> postgres=# insert into gtt values (generate_series(1,100000), generate_series(1,100000));
> INSERT 0 100000
> postgres=# create index on gtt(y);
> ERROR: can not create index when have one or more backend attached this global temp table
>
> I wonder why do you need such restriction?
>
>
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
> <global_temporary_table_v1-pg13.patch>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-07 09:40 Pavel Stehule <pavel.stehule@gmail.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2019-11-07 09:40 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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.
>
> > But right now this check is not working correctly in any case - if you
> insert some data into the table, then
> > you can not create index any more:
> >
> > postgres=# create global temp table gtt(x integer primary key, y
> integer);
> > CREATE TABLE
> > postgres=# insert into gtt values (generate_series(1,100000),
> generate_series(1,100000));
> > INSERT 0 100000
> > postgres=# create index on gtt(y);
> > ERROR: can not create index when have one or more backend attached this
> global temp table
> >
> > I wonder why do you need such restriction?
> >
> >
> > --
> > Konstantin Knizhnik
> > Postgres Professional: http://www.postgrespro.com
> > The Russian Postgres Company
> >
> > <global_temporary_table_v1-pg13.patch>
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-07 11:49 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2 siblings, 0 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2019-11-07 11:49 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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:
[application/octet-stream] global_temporary_table_v2.patch (136.5K, ../../4051F91D-0BD6-4423-909D-9B728E65425D@alibaba-inc.com/3-global_temporary_table_v2.patch)
download | inline diff:
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
=
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-07 12:17 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2019-11-07 12:17 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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.
>
>
>
> > But right now this check is not working correctly in any case - if you insert some data into the table, then
> > you can not create index any more:
> >
> > postgres=# create global temp table gtt(x integer primary key, y integer);
> > CREATE TABLE
> > postgres=# insert into gtt values (generate_series(1,100000), generate_series(1,100000));
> > INSERT 0 100000
> > postgres=# create index on gtt(y);
> > ERROR: can not create index when have one or more backend attached this global temp table
> >
> > I wonder why do you need such restriction?
> >
> >
> > --
> > Konstantin Knizhnik
> > Postgres Professional: http://www.postgrespro.com <http://www.postgrespro.com/;
> > The Russian Postgres Company
> >
> > <global_temporary_table_v1-pg13.patch>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-07 12:29 Pavel Stehule <pavel.stehule@gmail.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 0 replies; 525+ messages in thread
From: Pavel Stehule @ 2019-11-07 12:29 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
č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
> do alter GTT.
> I provide a function(pg_gtt_attached_pid(relation, schema)) to query which
> session a GTT is being used by.
>
>
>
>>
>> > But right now this check is not working correctly in any case - if you
>> insert some data into the table, then
>> > you can not create index any more:
>> >
>> > postgres=# create global temp table gtt(x integer primary key, y
>> integer);
>> > CREATE TABLE
>> > postgres=# insert into gtt values (generate_series(1,100000),
>> generate_series(1,100000));
>> > INSERT 0 100000
>> > postgres=# create index on gtt(y);
>> > ERROR: can not create index when have one or more backend attached
>> this global temp table
>> >
>> > I wonder why do you need such restriction?
>> >
>> >
>> > --
>> > Konstantin Knizhnik
>> > Postgres Professional: http://www.postgrespro.com
>> > The Russian Postgres Company
>> >
>> > <global_temporary_table_v1-pg13.patch>
>>
>>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-07 16:32 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-11-07 16:32 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-08 07:50 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2019-11-08 07:50 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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.
>
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
>
>
Attachments:
[application/octet-stream] global_temporary_table_v2-pg13.patch (136.1K, ../../F687C0B8-AE94-4A65-9AD9-BB83B874F425@alibaba-inc.com/2-global_temporary_table_v2-pg13.patch)
download | inline diff:
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
=
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-11-08 12:43 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-11-08 12:43 UTC (permalink / raw)
To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
As far as both Robert and Pavel think that aspects of using GTT in
parallel queries and at replica should be considered separately.
I have prepared simplest version of the patch for GTT which introduces
minimal differences with current (local) temporary table.
So GTT are stored in private buffers, can not be accessed at replica, in
prepared transactions and parallel queries.
But it supports all existed built-on indexes (hash, nbtree, btrin, git,
gist, spgist) and per-backend statistic.
There are no any DDL limitations for GTT.
Also I have not yet introduced pg_statistic view (as proposed by Pavel).
I afraid that it may break compatibility with some existed extensions
and applications.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
[text/x-patch] global_private_temp-5.patch (76.8K, ../../1e0a0954-2be5-b36c-c420-cdd0a381d641@postgrespro.ru/2-global_private_temp-5.patch)
download | inline diff:
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index ae7b729..485c068 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -672,7 +672,7 @@ brinbuild(Relation heap, Relation index, IndexInfo *indexInfo)
/*
* We expect to be called exactly once for any index relation.
*/
- if (RelationGetNumberOfBlocks(index) != 0)
+ if (RelationGetNumberOfBlocks(index) != 0 && index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
elog(ERROR, "index \"%s\" already contains data",
RelationGetRelationName(index));
@@ -681,9 +681,17 @@ brinbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* whole relation will be rolled back.
*/
- meta = ReadBuffer(index, P_NEW);
- Assert(BufferGetBlockNumber(meta) == BRIN_METAPAGE_BLKNO);
- LockBuffer(meta, BUFFER_LOCK_EXCLUSIVE);
+ if (index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
+ {
+ meta = ReadBuffer(index, P_NEW);
+ Assert(BufferGetBlockNumber(meta) == BRIN_METAPAGE_BLKNO);
+ LockBuffer(meta, BUFFER_LOCK_EXCLUSIVE);
+ }
+ else
+ {
+ meta = ReadBuffer(index, BRIN_METAPAGE_BLKNO);
+ LockBuffer(meta, BUFFER_LOCK_SHARE);
+ }
brin_metapage_init(BufferGetPage(meta), BrinGetPagesPerRange(index),
BRIN_CURRENT_VERSION);
diff --git a/src/backend/access/brin/brin_revmap.c b/src/backend/access/brin/brin_revmap.c
index 647350c..62e5212 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,13 @@ brinRevmapInitialize(Relation idxrel, BlockNumber *pagesPerRange,
meta = ReadBuffer(idxrel, BRIN_METAPAGE_BLKNO);
LockBuffer(meta, BUFFER_LOCK_SHARE);
page = BufferGetPage(meta);
+
+ if (GlobalTempRelationPageIsNotInitialized(idxrel, page))
+ {
+ Relation heap = RelationIdGetRelation(idxrel->rd_index->indrelid);
+ brinbuild(heap, idxrel, BuildIndexInfo(idxrel));
+ RelationClose(heap);
+ }
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..51fe102 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -241,6 +241,13 @@ ginHeapTupleFastInsert(GinState *ginstate, GinTupleCollector *collector)
metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
metapage = BufferGetPage(metabuffer);
+ if (GlobalTempRelationPageIsNotInitialized(index, metapage))
+ {
+ Relation heap = RelationIdGetRelation(index->rd_index->indrelid);
+ ginbuild(heap, index, BuildIndexInfo(index));
+ RelationClose(heap);
+ }
+
/*
* 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..975c186 100644
--- a/src/backend/access/gin/ginget.c
+++ b/src/backend/access/gin/ginget.c
@@ -1759,7 +1759,8 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
match;
int i;
pendingPosition pos;
- Buffer metabuffer = ReadBuffer(scan->indexRelation, GIN_METAPAGE_BLKNO);
+ Relation index = scan->indexRelation;
+ Buffer metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
Page page;
BlockNumber blkno;
@@ -1769,11 +1770,19 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
* Acquire predicate lock on the metapage, to conflict with any fastupdate
* insertions.
*/
- PredicateLockPage(scan->indexRelation, GIN_METAPAGE_BLKNO, scan->xs_snapshot);
+ PredicateLockPage(index, GIN_METAPAGE_BLKNO, scan->xs_snapshot);
LockBuffer(metabuffer, GIN_SHARE);
page = BufferGetPage(metabuffer);
- TestForOldSnapshot(scan->xs_snapshot, scan->indexRelation, page);
+ TestForOldSnapshot(scan->xs_snapshot, index, page);
+
+ if (GlobalTempRelationPageIsNotInitialized(index, page))
+ {
+ Relation heap = RelationIdGetRelation(index->rd_index->indrelid);
+ ginbuild(heap, index, BuildIndexInfo(index));
+ RelationClose(heap);
+ UnlockReleaseBuffer(metabuffer);
+ }
blkno = GinPageGetMeta(page)->head;
/*
@@ -1784,10 +1793,10 @@ 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);
+ pos.pendingBuffer = ReadBuffer(index, blkno);
LockBuffer(pos.pendingBuffer, GIN_SHARE);
pos.firstOffset = FirstOffsetNumber;
UnlockReleaseBuffer(metabuffer);
diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c
index 55eab14..d6739f3 100644
--- a/src/backend/access/gin/gininsert.c
+++ b/src/backend/access/gin/gininsert.c
@@ -328,7 +328,7 @@ ginbuild(Relation heap, Relation index, IndexInfo *indexInfo)
MemoryContext oldCtx;
OffsetNumber attnum;
- if (RelationGetNumberOfBlocks(index) != 0)
+ if (RelationGetNumberOfBlocks(index) != 0 && index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
elog(ERROR, "index \"%s\" already contains data",
RelationGetRelationName(index));
@@ -337,7 +337,15 @@ ginbuild(Relation heap, Relation index, IndexInfo *indexInfo)
memset(&buildstate.buildStats, 0, sizeof(GinStatsData));
/* initialize the meta page */
- MetaBuffer = GinNewBuffer(index);
+ if (index->rd_rel->relpersistence == RELPERSISTENCE_SESSION)
+ {
+ MetaBuffer = ReadBuffer(index, 0);
+ LockBuffer(MetaBuffer, GIN_SHARE);
+ }
+ else
+ {
+ MetaBuffer = GinNewBuffer(index);
+ }
/* initialize the root page */
RootBuffer = GinNewBuffer(index);
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 0cc8791..bcde5ea 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -16,6 +16,7 @@
#include "access/gist_private.h"
#include "access/gistscan.h"
+#include "catalog/index.h"
#include "catalog/pg_collation.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
@@ -677,7 +678,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)))
+ gistbuild(heapRel, r, BuildIndexInfo(r));
+ else
+ gistcheckpage(state.r, stack->buffer);
}
stack->page = (Page) BufferGetPage(stack->buffer);
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 2f4543d..8d194c8 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -156,7 +156,7 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* We expect to be called exactly once for any index relation. If that's
* not the case, big trouble's what we have.
*/
- if (RelationGetNumberOfBlocks(index) != 0)
+ if (RelationGetNumberOfBlocks(index) != 0 && index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
elog(ERROR, "index \"%s\" already contains data",
RelationGetRelationName(index));
@@ -171,8 +171,16 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
buildstate.giststate->tempCxt = createTempGistContext();
/* initialize the root page */
- buffer = gistNewBuffer(index);
- Assert(BufferGetBlockNumber(buffer) == GIST_ROOT_BLKNO);
+ if (index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
+ {
+ buffer = gistNewBuffer(index);
+ Assert(BufferGetBlockNumber(buffer) == GIST_ROOT_BLKNO);
+ }
+ else
+ {
+ buffer = ReadBuffer(index, GIST_ROOT_BLKNO);
+ LockBuffer(buffer, GIST_SHARE);
+ }
page = BufferGetPage(buffer);
START_CRIT_SECTION();
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index 22d790d..5560a41 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -17,8 +17,10 @@
#include "access/genam.h"
#include "access/gist_private.h"
#include "access/relscan.h"
+#include "catalog/index.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
+#include "storage/freespace.h"
#include "storage/predicate.h"
#include "pgstat.h"
#include "lib/pairingheap.h"
@@ -344,7 +346,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)))
+ gistbuild(scan->heapRelation, r, BuildIndexInfo(r));
+ 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/hash.c b/src/backend/access/hash/hash.c
index 5cc30da..1b228db 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -119,7 +119,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* We expect to be called exactly once for any index relation. If that's
* not the case, big trouble's what we have.
*/
- if (RelationGetNumberOfBlocks(index) != 0)
+ if (RelationGetNumberOfBlocks(index) != 0 && index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
elog(ERROR, "index \"%s\" already contains data",
RelationGetRelationName(index));
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index 838ee68..00ba123 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -75,13 +75,22 @@ _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)))
+ {
+ Relation heap = RelationIdGetRelation(rel->rd_index->indrelid);
+ hashbuild(heap, rel, BuildIndexInfo(rel));
+ RelationClose(heap);
+ if (access != HASH_NOLOCK)
+ LockBuffer(buf, access);
+ }
+ else
+ {
+ if (access != HASH_NOLOCK)
+ LockBuffer(buf, access);
+ _hash_checkpage(rel, buf, flags);
+ }
return buf;
}
@@ -339,7 +348,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..eff9e10 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -27,8 +27,10 @@
#include "access/transam.h"
#include "access/xlog.h"
#include "access/xloginsert.h"
+#include "catalog/index.h"
#include "miscadmin.h"
#include "storage/indexfsm.h"
+#include "storage/buf_internals.h"
#include "storage/lmgr.h"
#include "storage/predicate.h"
#include "utils/snapmgr.h"
@@ -762,8 +764,22 @@ _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)))
+ {
+ 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);
+ }
}
else
{
diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index ab19692..227bc19 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -330,7 +330,7 @@ btbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* We expect to be called exactly once for any index relation. If that's
* not the case, big trouble's what we have.
*/
- if (RelationGetNumberOfBlocks(index) != 0)
+ if (RelationGetNumberOfBlocks(index) != 0 && index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
elog(ERROR, "index \"%s\" already contains data",
RelationGetRelationName(index));
diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/spgist/spginsert.c
index b40bd44..f44bec7 100644
--- a/src/backend/access/spgist/spginsert.c
+++ b/src/backend/access/spgist/spginsert.c
@@ -81,21 +81,32 @@ spgbuild(Relation heap, Relation index, IndexInfo *indexInfo)
rootbuffer,
nullbuffer;
- if (RelationGetNumberOfBlocks(index) != 0)
- elog(ERROR, "index \"%s\" already contains data",
- RelationGetRelationName(index));
-
- /*
- * Initialize the meta page and root pages
- */
- metabuffer = SpGistNewBuffer(index);
- rootbuffer = SpGistNewBuffer(index);
- nullbuffer = SpGistNewBuffer(index);
-
- Assert(BufferGetBlockNumber(metabuffer) == SPGIST_METAPAGE_BLKNO);
- Assert(BufferGetBlockNumber(rootbuffer) == SPGIST_ROOT_BLKNO);
- Assert(BufferGetBlockNumber(nullbuffer) == SPGIST_NULL_BLKNO);
-
+ if (index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
+ {
+ if (RelationGetNumberOfBlocks(index) != 0)
+ elog(ERROR, "index \"%s\" already contains data",
+ RelationGetRelationName(index));
+
+ /*
+ * Initialize the meta page and root pages
+ */
+ metabuffer = SpGistNewBuffer(index);
+ rootbuffer = SpGistNewBuffer(index);
+ nullbuffer = SpGistNewBuffer(index);
+
+ Assert(BufferGetBlockNumber(metabuffer) == SPGIST_METAPAGE_BLKNO);
+ Assert(BufferGetBlockNumber(rootbuffer) == SPGIST_ROOT_BLKNO);
+ Assert(BufferGetBlockNumber(nullbuffer) == SPGIST_NULL_BLKNO);
+ }
+ else
+ {
+ metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
+ rootbuffer = ReadBuffer(index, SPGIST_ROOT_BLKNO);
+ nullbuffer = ReadBuffer(index, SPGIST_NULL_BLKNO);
+ LockBuffer(metabuffer, BUFFER_LOCK_SHARE);
+ LockBuffer(rootbuffer, BUFFER_LOCK_SHARE);
+ LockBuffer(nullbuffer, BUFFER_LOCK_SHARE);
+ }
START_CRIT_SECTION();
SpGistInitMetapage(BufferGetPage(metabuffer));
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index 45472db..ea15964 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -21,6 +21,7 @@
#include "access/spgist_private.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/index.h"
#include "catalog/pg_amop.h"
#include "storage/bufmgr.h"
#include "storage/indexfsm.h"
@@ -106,6 +107,7 @@ spgGetCache(Relation index)
spgConfigIn in;
FmgrInfo *procinfo;
Buffer metabuffer;
+ Page metapage;
SpGistMetaPageData *metadata;
cache = MemoryContextAllocZero(index->rd_indexcxt,
@@ -155,12 +157,21 @@ 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))
+ {
+ Relation heap = RelationIdGetRelation(index->rd_index->indrelid);
+ spgbuild(heap, index, BuildIndexInfo(index));
+ RelationClose(heap);
+ }
+ 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/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index db3a68a..60212b0 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,6 +48,7 @@
#include "partitioning/partprune.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
+#include "utils/rel.h"
/* results of subquery_is_pushdown_safe */
@@ -618,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* the rest of the necessary infrastructure right now anyway. So
* for now, bail out if we see a temporary table.
*/
- if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+ if (IsLocalRelpersistence(get_rel_persistence(rte->relid)))
return;
/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 17c5f08..7c83e7b 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,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 (RelationHasSessionScope(heap) ||
!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
{
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;
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-08 12:57 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 0 replies; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-11-08 12:57 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-08 15:06 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
1 sibling, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2019-11-08 15:06 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
> 2019年11月1日 下午11:15,Konstantin Knizhnik <k.knizhnik@postgrespro.ru> 写道:
>
>
>
> On 25.10.2019 20:00, Pavel Stehule wrote:
>>
>> >
>> >> So except the limitation mentioned above (which I do not consider as critical) there is only one problem which was not addressed: maintaining statistics for GTT.
>> >> If all of the following conditions are true:
>> >>
>> >> 1) GTT are used in joins
>> >> 2) There are indexes defined for GTT
>> >> 3) Size and histogram of GTT in different backends can significantly vary.
>> >> 4) ANALYZE was explicitly called for GTT
>> >>
>> >> then query execution plan built in one backend will be also used for other backends where it can be inefficient.
>> >> I also do not consider this problem as "show stopper" for adding GTT to Postgres.
>> > I think that's *definitely* a show stopper.
>> Well, if both you and Pavel think that it is really "show stopper", then
>> this problem really has to be addressed.
>> I slightly confused about this opinion, because Pavel has told me
>> himself that 99% of users never create indexes for temp tables
>> or run "analyze" for them. And without it, this problem is not a problem
>> at all.
>>
>>
>> Users doesn't do ANALYZE on temp tables in 99%. It's true. But second fact is so users has lot of problems. It's very similar to wrong statistics on persistent tables. When data are small, then it is not problem for users, although from my perspective it's not optimal. When data are not small, then the problem can be brutal. Temporary tables are not a exception. And users and developers are people - we know only about fatal problems. There are lot of unoptimized queries, but because the problem is not fatal, then it is not reason for report it. And lot of people has not any idea how fast the databases can be. The knowledges of users and app developers are sad book.
>>
>> Pavel
>
> It seems to me that I have found quite elegant solution for per-backend statistic for GTT: I just inserting it in backend's catalog cache, but not in pg_statistic table itself.
> To do it I have to add InsertSysCache/InsertCatCache functions which insert pinned entry in the correspondent cache.
> I wonder if there are some pitfalls of such approach?
>
> New patch for GTT is attached.
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com <http://www.postgrespro.com/;
> The Russian Postgres Company
> <global_private_temp-4.patch>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-11-11 14:54 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-11-11 14:54 UTC (permalink / raw)
To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
Yet another version of my GTT patch addressing issues reported by
曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
* Bug in TRUNCATE is fixed,
* ON COMMIT DELETE ROWS option is supported
* ALTER TABLE is correctly handled
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
[text/x-patch] global_private_temp-6.patch (83.1K, ../../d308ce2b-f05f-307a-c1ab-663da9ea288d@postgrespro.ru/2-global_private_temp-6.patch)
download | inline diff:
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index ae7b729..485c068 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -672,7 +672,7 @@ brinbuild(Relation heap, Relation index, IndexInfo *indexInfo)
/*
* We expect to be called exactly once for any index relation.
*/
- if (RelationGetNumberOfBlocks(index) != 0)
+ if (RelationGetNumberOfBlocks(index) != 0 && index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
elog(ERROR, "index \"%s\" already contains data",
RelationGetRelationName(index));
@@ -681,9 +681,17 @@ brinbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* whole relation will be rolled back.
*/
- meta = ReadBuffer(index, P_NEW);
- Assert(BufferGetBlockNumber(meta) == BRIN_METAPAGE_BLKNO);
- LockBuffer(meta, BUFFER_LOCK_EXCLUSIVE);
+ if (index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
+ {
+ meta = ReadBuffer(index, P_NEW);
+ Assert(BufferGetBlockNumber(meta) == BRIN_METAPAGE_BLKNO);
+ LockBuffer(meta, BUFFER_LOCK_EXCLUSIVE);
+ }
+ else
+ {
+ meta = ReadBuffer(index, BRIN_METAPAGE_BLKNO);
+ LockBuffer(meta, BUFFER_LOCK_SHARE);
+ }
brin_metapage_init(BufferGetPage(meta), BrinGetPagesPerRange(index),
BRIN_CURRENT_VERSION);
diff --git a/src/backend/access/brin/brin_revmap.c b/src/backend/access/brin/brin_revmap.c
index 647350c..62e5212 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,13 @@ brinRevmapInitialize(Relation idxrel, BlockNumber *pagesPerRange,
meta = ReadBuffer(idxrel, BRIN_METAPAGE_BLKNO);
LockBuffer(meta, BUFFER_LOCK_SHARE);
page = BufferGetPage(meta);
+
+ if (GlobalTempRelationPageIsNotInitialized(idxrel, page))
+ {
+ Relation heap = RelationIdGetRelation(idxrel->rd_index->indrelid);
+ brinbuild(heap, idxrel, BuildIndexInfo(idxrel));
+ RelationClose(heap);
+ }
TestForOldSnapshot(snapshot, idxrel, page);
metadata = (BrinMetaPageData *) PageGetContents(page);
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index b5072c0..650f31a 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
+ },
+ false
+ },
/* list terminator */
{{NULL}}
};
@@ -1478,6 +1491,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
StdRdOptions *rdopts;
int numoptions;
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/gin/ginfast.c b/src/backend/access/gin/ginfast.c
index 439a91b..51fe102 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -241,6 +241,13 @@ ginHeapTupleFastInsert(GinState *ginstate, GinTupleCollector *collector)
metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
metapage = BufferGetPage(metabuffer);
+ if (GlobalTempRelationPageIsNotInitialized(index, metapage))
+ {
+ Relation heap = RelationIdGetRelation(index->rd_index->indrelid);
+ ginbuild(heap, index, BuildIndexInfo(index));
+ RelationClose(heap);
+ }
+
/*
* 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..975c186 100644
--- a/src/backend/access/gin/ginget.c
+++ b/src/backend/access/gin/ginget.c
@@ -1759,7 +1759,8 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
match;
int i;
pendingPosition pos;
- Buffer metabuffer = ReadBuffer(scan->indexRelation, GIN_METAPAGE_BLKNO);
+ Relation index = scan->indexRelation;
+ Buffer metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
Page page;
BlockNumber blkno;
@@ -1769,11 +1770,19 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
* Acquire predicate lock on the metapage, to conflict with any fastupdate
* insertions.
*/
- PredicateLockPage(scan->indexRelation, GIN_METAPAGE_BLKNO, scan->xs_snapshot);
+ PredicateLockPage(index, GIN_METAPAGE_BLKNO, scan->xs_snapshot);
LockBuffer(metabuffer, GIN_SHARE);
page = BufferGetPage(metabuffer);
- TestForOldSnapshot(scan->xs_snapshot, scan->indexRelation, page);
+ TestForOldSnapshot(scan->xs_snapshot, index, page);
+
+ if (GlobalTempRelationPageIsNotInitialized(index, page))
+ {
+ Relation heap = RelationIdGetRelation(index->rd_index->indrelid);
+ ginbuild(heap, index, BuildIndexInfo(index));
+ RelationClose(heap);
+ UnlockReleaseBuffer(metabuffer);
+ }
blkno = GinPageGetMeta(page)->head;
/*
@@ -1784,10 +1793,10 @@ 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);
+ pos.pendingBuffer = ReadBuffer(index, blkno);
LockBuffer(pos.pendingBuffer, GIN_SHARE);
pos.firstOffset = FirstOffsetNumber;
UnlockReleaseBuffer(metabuffer);
diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c
index 55eab14..d6739f3 100644
--- a/src/backend/access/gin/gininsert.c
+++ b/src/backend/access/gin/gininsert.c
@@ -328,7 +328,7 @@ ginbuild(Relation heap, Relation index, IndexInfo *indexInfo)
MemoryContext oldCtx;
OffsetNumber attnum;
- if (RelationGetNumberOfBlocks(index) != 0)
+ if (RelationGetNumberOfBlocks(index) != 0 && index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
elog(ERROR, "index \"%s\" already contains data",
RelationGetRelationName(index));
@@ -337,7 +337,15 @@ ginbuild(Relation heap, Relation index, IndexInfo *indexInfo)
memset(&buildstate.buildStats, 0, sizeof(GinStatsData));
/* initialize the meta page */
- MetaBuffer = GinNewBuffer(index);
+ if (index->rd_rel->relpersistence == RELPERSISTENCE_SESSION)
+ {
+ MetaBuffer = ReadBuffer(index, 0);
+ LockBuffer(MetaBuffer, GIN_SHARE);
+ }
+ else
+ {
+ MetaBuffer = GinNewBuffer(index);
+ }
/* initialize the root page */
RootBuffer = GinNewBuffer(index);
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 0cc8791..bcde5ea 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -16,6 +16,7 @@
#include "access/gist_private.h"
#include "access/gistscan.h"
+#include "catalog/index.h"
#include "catalog/pg_collation.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
@@ -677,7 +678,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)))
+ gistbuild(heapRel, r, BuildIndexInfo(r));
+ else
+ gistcheckpage(state.r, stack->buffer);
}
stack->page = (Page) BufferGetPage(stack->buffer);
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 2f4543d..8d194c8 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -156,7 +156,7 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* We expect to be called exactly once for any index relation. If that's
* not the case, big trouble's what we have.
*/
- if (RelationGetNumberOfBlocks(index) != 0)
+ if (RelationGetNumberOfBlocks(index) != 0 && index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
elog(ERROR, "index \"%s\" already contains data",
RelationGetRelationName(index));
@@ -171,8 +171,16 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
buildstate.giststate->tempCxt = createTempGistContext();
/* initialize the root page */
- buffer = gistNewBuffer(index);
- Assert(BufferGetBlockNumber(buffer) == GIST_ROOT_BLKNO);
+ if (index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
+ {
+ buffer = gistNewBuffer(index);
+ Assert(BufferGetBlockNumber(buffer) == GIST_ROOT_BLKNO);
+ }
+ else
+ {
+ buffer = ReadBuffer(index, GIST_ROOT_BLKNO);
+ LockBuffer(buffer, GIST_SHARE);
+ }
page = BufferGetPage(buffer);
START_CRIT_SECTION();
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index 22d790d..5560a41 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -17,8 +17,10 @@
#include "access/genam.h"
#include "access/gist_private.h"
#include "access/relscan.h"
+#include "catalog/index.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
+#include "storage/freespace.h"
#include "storage/predicate.h"
#include "pgstat.h"
#include "lib/pairingheap.h"
@@ -344,7 +346,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)))
+ gistbuild(scan->heapRelation, r, BuildIndexInfo(r));
+ 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/hash.c b/src/backend/access/hash/hash.c
index 5cc30da..1b228db 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -119,7 +119,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* We expect to be called exactly once for any index relation. If that's
* not the case, big trouble's what we have.
*/
- if (RelationGetNumberOfBlocks(index) != 0)
+ if (RelationGetNumberOfBlocks(index) != 0 && index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
elog(ERROR, "index \"%s\" already contains data",
RelationGetRelationName(index));
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index 838ee68..00ba123 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -75,13 +75,22 @@ _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)))
+ {
+ Relation heap = RelationIdGetRelation(rel->rd_index->indrelid);
+ hashbuild(heap, rel, BuildIndexInfo(rel));
+ RelationClose(heap);
+ if (access != HASH_NOLOCK)
+ LockBuffer(buf, access);
+ }
+ else
+ {
+ if (access != HASH_NOLOCK)
+ LockBuffer(buf, access);
+ _hash_checkpage(rel, buf, flags);
+ }
return buf;
}
@@ -339,7 +348,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..eff9e10 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -27,8 +27,10 @@
#include "access/transam.h"
#include "access/xlog.h"
#include "access/xloginsert.h"
+#include "catalog/index.h"
#include "miscadmin.h"
#include "storage/indexfsm.h"
+#include "storage/buf_internals.h"
#include "storage/lmgr.h"
#include "storage/predicate.h"
#include "utils/snapmgr.h"
@@ -762,8 +764,22 @@ _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)))
+ {
+ 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);
+ }
}
else
{
diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index ab19692..227bc19 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -330,7 +330,7 @@ btbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* We expect to be called exactly once for any index relation. If that's
* not the case, big trouble's what we have.
*/
- if (RelationGetNumberOfBlocks(index) != 0)
+ if (RelationGetNumberOfBlocks(index) != 0 && index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
elog(ERROR, "index \"%s\" already contains data",
RelationGetRelationName(index));
diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/spgist/spginsert.c
index b40bd44..f44bec7 100644
--- a/src/backend/access/spgist/spginsert.c
+++ b/src/backend/access/spgist/spginsert.c
@@ -81,21 +81,32 @@ spgbuild(Relation heap, Relation index, IndexInfo *indexInfo)
rootbuffer,
nullbuffer;
- if (RelationGetNumberOfBlocks(index) != 0)
- elog(ERROR, "index \"%s\" already contains data",
- RelationGetRelationName(index));
-
- /*
- * Initialize the meta page and root pages
- */
- metabuffer = SpGistNewBuffer(index);
- rootbuffer = SpGistNewBuffer(index);
- nullbuffer = SpGistNewBuffer(index);
-
- Assert(BufferGetBlockNumber(metabuffer) == SPGIST_METAPAGE_BLKNO);
- Assert(BufferGetBlockNumber(rootbuffer) == SPGIST_ROOT_BLKNO);
- Assert(BufferGetBlockNumber(nullbuffer) == SPGIST_NULL_BLKNO);
-
+ if (index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
+ {
+ if (RelationGetNumberOfBlocks(index) != 0)
+ elog(ERROR, "index \"%s\" already contains data",
+ RelationGetRelationName(index));
+
+ /*
+ * Initialize the meta page and root pages
+ */
+ metabuffer = SpGistNewBuffer(index);
+ rootbuffer = SpGistNewBuffer(index);
+ nullbuffer = SpGistNewBuffer(index);
+
+ Assert(BufferGetBlockNumber(metabuffer) == SPGIST_METAPAGE_BLKNO);
+ Assert(BufferGetBlockNumber(rootbuffer) == SPGIST_ROOT_BLKNO);
+ Assert(BufferGetBlockNumber(nullbuffer) == SPGIST_NULL_BLKNO);
+ }
+ else
+ {
+ metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
+ rootbuffer = ReadBuffer(index, SPGIST_ROOT_BLKNO);
+ nullbuffer = ReadBuffer(index, SPGIST_NULL_BLKNO);
+ LockBuffer(metabuffer, BUFFER_LOCK_SHARE);
+ LockBuffer(rootbuffer, BUFFER_LOCK_SHARE);
+ LockBuffer(nullbuffer, BUFFER_LOCK_SHARE);
+ }
START_CRIT_SECTION();
SpGistInitMetapage(BufferGetPage(metabuffer));
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index 45472db..ea15964 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -21,6 +21,7 @@
#include "access/spgist_private.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/index.h"
#include "catalog/pg_amop.h"
#include "storage/bufmgr.h"
#include "storage/indexfsm.h"
@@ -106,6 +107,7 @@ spgGetCache(Relation index)
spgConfigIn in;
FmgrInfo *procinfo;
Buffer metabuffer;
+ Page metapage;
SpGistMetaPageData *metadata;
cache = MemoryContextAllocZero(index->rd_indexcxt,
@@ -155,12 +157,21 @@ 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))
+ {
+ Relation heap = RelationIdGetRelation(index->rd_index->indrelid);
+ spgbuild(heap, index, BuildIndexInfo(index));
+ RelationClose(heap);
+ }
+ 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..21d5a30 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12,6 +12,9 @@
*
*-------------------------------------------------------------------------
*/
+#include <sys/stat.h>
+#include <unistd.h>
+
#include "postgres.h"
#include "access/genam.h"
@@ -533,6 +536,23 @@ static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+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;
+}
+
+
/* ----------------------------------------------------------------
* DefineRelation
* Creates a new relation.
@@ -576,6 +596,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
@@ -587,7 +608,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")));
@@ -613,17 +634,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
RangeVarGetAndCheckCreationNamespace(stmt->relation, NoLock, NULL);
/*
- * Security check: disallow creating temp tables from security-restricted
- * 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
- && InSecurityRestrictedOperation())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("cannot create temporary table within security-restricted operation")));
-
- /*
* Determine the lockmode to use when scanning parents. A self-exclusive
* lock is needed here.
*
@@ -718,6 +728,38 @@ 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_SESSION)
+ {
+ 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 if (stmt->oncommit != ONCOMMIT_NOOP)
+ {
+ 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
+ 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 +1814,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);
@@ -3449,6 +3492,26 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
(void *) stmt);
}
+
+static bool
+CheckGlobalTempTableNotInUse(Relation rel)
+{
+ int id;
+ for (id = 1; id <= MaxBackends; id++)
+ {
+ if (id != MyBackendId)
+ {
+ struct stat fst;
+ char* path = relpathbackend(rel->rd_node, id, MAIN_FORKNUM);
+ int rc = stat(path, &fst);
+ pfree(path);
+ if (rc == 0 && fst.st_size != 0)
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* AlterTable
* Execute ALTER TABLE, which can be a list of subcommands
@@ -3500,6 +3563,9 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
rel = relation_open(relid, NoLock);
CheckTableNotInUse(rel, "ALTER TABLE");
+ if (rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION
+ && !CheckGlobalTempTableNotInUse(rel))
+ elog(ERROR, "Global temp table used by active backends can not be altered");
ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode);
}
@@ -7708,6 +7774,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 +14212,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 +14706,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/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index db3a68a..60212b0 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,6 +48,7 @@
#include "partitioning/partprune.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
+#include "utils/rel.h"
/* results of subquery_is_pushdown_safe */
@@ -618,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* the rest of the necessary infrastructure right now anyway. So
* for now, bail out if we see a temporary table.
*/
- if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+ if (IsLocalRelpersistence(get_rel_persistence(rte->relid)))
return;
/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 17c5f08..7c83e7b 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,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 (RelationHasSessionScope(heap) ||
!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
{
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/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 483f705..1129dc3 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -2933,7 +2933,7 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber *forkNum,
/* If it's a local relation, it's localbuf.c's problem. */
if (RelFileNodeBackendIsTemp(rnode))
{
- if (rnode.backend == MyBackendId)
+ if (GetRelationBackendId(rnode.backend) == MyBackendId)
{
for (j = 0; j < nforks; j++)
DropRelFileNodeLocalBuffers(rnode.node, forkNum[j],
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 07f3c93..8cf06f6 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -28,17 +28,20 @@
#include "miscadmin.h"
#include "access/xlogutils.h"
#include "access/xlog.h"
+#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "pgstat.h"
#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"
#include "storage/sync.h"
#include "utils/hsearch.h"
#include "utils/memutils.h"
+#include "utils/rel.h"
#include "pg_trace.h"
/*
@@ -87,6 +90,19 @@ 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;
+ ForkNumber forknum;
+ 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 +168,60 @@ 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, rel->forknum, 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, ForkNumber forknum)
+{
+ 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->forknum = forknum;
+ rel->next = SessionRelations;
+ SessionRelations = rel;
+}
+
+static void
+RegisterOnCommitAction(SMgrRelation reln, ForkNumber forknum)
+{
+ if (reln->smgr_owner && forknum == MAIN_FORKNUM)
+ {
+ Relation rel = (Relation)((char*)reln->smgr_owner - offsetof(RelationData, rd_smgr));
+ if (rel->rd_options
+ && ((StdRdOptions *)rel->rd_options)->on_commit_delete_rows)
+ {
+ register_on_commit_action(rel->rd_id, ONCOMMIT_DELETE_ROWS);
+ }
+ }
+}
+
/*
* mdexists() -- Does the physical file exist?
*
@@ -218,6 +288,8 @@ mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo)
errmsg("could not create file \"%s\": %m", path)));
}
}
+ if (RelFileNodeBackendIsGlobalTemp(reln->smgr_rnode))
+ RegisterSessionRelation(reln, forkNum);
pfree(path);
@@ -465,6 +537,21 @@ 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, forknum);
+ if (!(behavior & EXTENSION_RETURN_NULL))
+ RegisterOnCommitAction(reln, forknum);
+ goto NewSegment;
+ }
+ }
if ((behavior & EXTENSION_RETURN_NULL) &&
FILE_POSSIBLY_DELETED(errno))
{
@@ -476,6 +563,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 +740,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),
@@ -743,7 +836,8 @@ mdnblocks(SMgrRelation reln, ForkNumber forknum)
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..11b4b89 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,12 @@ extern PGDLLIMPORT BackendId ParallelMasterBackendId;
#define BackendIdForTempRelations() \
(ParallelMasterBackendId == InvalidBackendId ? MyBackendId : ParallelMasterBackendId)
+
+#define BackendIdForSessionRelations() \
+ (BackendIdForTempRelations() + SessionRelFirstBackendId)
+
+#define IsSessionRelationBackendId(id) ((id) >= SessionRelFirstBackendId)
+
+#define GetRelationBackendId(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..a30137f 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
@@ -327,6 +328,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 +348,7 @@ typedef enum ViewOptCheckOption
VIEW_OPTION_CHECK_OPTION_CASCADED
} ViewOptCheckOption;
+
/*
* ViewOptions
* Contents of rd_options for views
@@ -526,7 +540,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;
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2019-11-11 15:19 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-11-11 15:19 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-11-20 16:32 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-11-20 16:32 UTC (permalink / raw)
To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
Now pg_gtt_statistic view is provided for global temp tables.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
[text/x-patch] global_private_temp-7.patch (90.5K, ../../a59bb226-f6a8-bbcd-0657-727f656ac550@postgrespro.ru/2-global_private_temp-7.patch)
download | inline diff:
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index ae7b729..485c068 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -672,7 +672,7 @@ brinbuild(Relation heap, Relation index, IndexInfo *indexInfo)
/*
* We expect to be called exactly once for any index relation.
*/
- if (RelationGetNumberOfBlocks(index) != 0)
+ if (RelationGetNumberOfBlocks(index) != 0 && index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
elog(ERROR, "index \"%s\" already contains data",
RelationGetRelationName(index));
@@ -681,9 +681,17 @@ brinbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* whole relation will be rolled back.
*/
- meta = ReadBuffer(index, P_NEW);
- Assert(BufferGetBlockNumber(meta) == BRIN_METAPAGE_BLKNO);
- LockBuffer(meta, BUFFER_LOCK_EXCLUSIVE);
+ if (index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
+ {
+ meta = ReadBuffer(index, P_NEW);
+ Assert(BufferGetBlockNumber(meta) == BRIN_METAPAGE_BLKNO);
+ LockBuffer(meta, BUFFER_LOCK_EXCLUSIVE);
+ }
+ else
+ {
+ meta = ReadBuffer(index, BRIN_METAPAGE_BLKNO);
+ LockBuffer(meta, BUFFER_LOCK_SHARE);
+ }
brin_metapage_init(BufferGetPage(meta), BrinGetPagesPerRange(index),
BRIN_CURRENT_VERSION);
diff --git a/src/backend/access/brin/brin_revmap.c b/src/backend/access/brin/brin_revmap.c
index 647350c..d432fec 100644
--- a/src/backend/access/brin/brin_revmap.c
+++ b/src/backend/access/brin/brin_revmap.c
@@ -25,8 +25,10 @@
#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 "catalog/index.h"
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
@@ -79,6 +81,13 @@ brinRevmapInitialize(Relation idxrel, BlockNumber *pagesPerRange,
meta = ReadBuffer(idxrel, BRIN_METAPAGE_BLKNO);
LockBuffer(meta, BUFFER_LOCK_SHARE);
page = BufferGetPage(meta);
+
+ if (GlobalTempRelationPageIsNotInitialized(idxrel, page))
+ {
+ Relation heap = RelationIdGetRelation(idxrel->rd_index->indrelid);
+ brinbuild(heap, idxrel, BuildIndexInfo(idxrel));
+ RelationClose(heap);
+ }
TestForOldSnapshot(snapshot, idxrel, page);
metadata = (BrinMetaPageData *) PageGetContents(page);
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index b5072c0..650f31a 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
+ },
+ false
+ },
/* list terminator */
{{NULL}}
};
@@ -1478,6 +1491,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
StdRdOptions *rdopts;
int numoptions;
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/gin/ginfast.c b/src/backend/access/gin/ginfast.c
index 439a91b..06fb671 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -23,6 +23,7 @@
#include "access/xloginsert.h"
#include "access/xlog.h"
#include "commands/vacuum.h"
+#include "catalog/index.h"
#include "catalog/pg_am.h"
#include "miscadmin.h"
#include "utils/memutils.h"
@@ -241,6 +242,13 @@ ginHeapTupleFastInsert(GinState *ginstate, GinTupleCollector *collector)
metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
metapage = BufferGetPage(metabuffer);
+ if (GlobalTempRelationPageIsNotInitialized(index, metapage))
+ {
+ Relation heap = RelationIdGetRelation(index->rd_index->indrelid);
+ ginbuild(heap, index, BuildIndexInfo(index));
+ RelationClose(heap);
+ }
+
/*
* 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..a7ad4c6 100644
--- a/src/backend/access/gin/ginget.c
+++ b/src/backend/access/gin/ginget.c
@@ -16,6 +16,7 @@
#include "access/gin_private.h"
#include "access/relscan.h"
+#include "catalog/index.h"
#include "miscadmin.h"
#include "storage/predicate.h"
#include "utils/datum.h"
@@ -1759,7 +1760,8 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
match;
int i;
pendingPosition pos;
- Buffer metabuffer = ReadBuffer(scan->indexRelation, GIN_METAPAGE_BLKNO);
+ Relation index = scan->indexRelation;
+ Buffer metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
Page page;
BlockNumber blkno;
@@ -1769,11 +1771,19 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
* Acquire predicate lock on the metapage, to conflict with any fastupdate
* insertions.
*/
- PredicateLockPage(scan->indexRelation, GIN_METAPAGE_BLKNO, scan->xs_snapshot);
+ PredicateLockPage(index, GIN_METAPAGE_BLKNO, scan->xs_snapshot);
LockBuffer(metabuffer, GIN_SHARE);
page = BufferGetPage(metabuffer);
- TestForOldSnapshot(scan->xs_snapshot, scan->indexRelation, page);
+ TestForOldSnapshot(scan->xs_snapshot, index, page);
+
+ if (GlobalTempRelationPageIsNotInitialized(index, page))
+ {
+ Relation heap = RelationIdGetRelation(index->rd_index->indrelid);
+ ginbuild(heap, index, BuildIndexInfo(index));
+ RelationClose(heap);
+ UnlockReleaseBuffer(metabuffer);
+ }
blkno = GinPageGetMeta(page)->head;
/*
@@ -1784,10 +1794,9 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
{
/* No pending list, so proceed with normal scan */
UnlockReleaseBuffer(metabuffer);
- return;
}
- pos.pendingBuffer = ReadBuffer(scan->indexRelation, blkno);
+ pos.pendingBuffer = ReadBuffer(index, blkno);
LockBuffer(pos.pendingBuffer, GIN_SHARE);
pos.firstOffset = FirstOffsetNumber;
UnlockReleaseBuffer(metabuffer);
diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c
index 55eab14..d6739f3 100644
--- a/src/backend/access/gin/gininsert.c
+++ b/src/backend/access/gin/gininsert.c
@@ -328,7 +328,7 @@ ginbuild(Relation heap, Relation index, IndexInfo *indexInfo)
MemoryContext oldCtx;
OffsetNumber attnum;
- if (RelationGetNumberOfBlocks(index) != 0)
+ if (RelationGetNumberOfBlocks(index) != 0 && index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
elog(ERROR, "index \"%s\" already contains data",
RelationGetRelationName(index));
@@ -337,7 +337,15 @@ ginbuild(Relation heap, Relation index, IndexInfo *indexInfo)
memset(&buildstate.buildStats, 0, sizeof(GinStatsData));
/* initialize the meta page */
- MetaBuffer = GinNewBuffer(index);
+ if (index->rd_rel->relpersistence == RELPERSISTENCE_SESSION)
+ {
+ MetaBuffer = ReadBuffer(index, 0);
+ LockBuffer(MetaBuffer, GIN_SHARE);
+ }
+ else
+ {
+ MetaBuffer = GinNewBuffer(index);
+ }
/* initialize the root page */
RootBuffer = GinNewBuffer(index);
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 0cc8791..bcde5ea 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -16,6 +16,7 @@
#include "access/gist_private.h"
#include "access/gistscan.h"
+#include "catalog/index.h"
#include "catalog/pg_collation.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
@@ -677,7 +678,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)))
+ gistbuild(heapRel, r, BuildIndexInfo(r));
+ else
+ gistcheckpage(state.r, stack->buffer);
}
stack->page = (Page) BufferGetPage(stack->buffer);
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 2f4543d..8d194c8 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -156,7 +156,7 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* We expect to be called exactly once for any index relation. If that's
* not the case, big trouble's what we have.
*/
- if (RelationGetNumberOfBlocks(index) != 0)
+ if (RelationGetNumberOfBlocks(index) != 0 && index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
elog(ERROR, "index \"%s\" already contains data",
RelationGetRelationName(index));
@@ -171,8 +171,16 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
buildstate.giststate->tempCxt = createTempGistContext();
/* initialize the root page */
- buffer = gistNewBuffer(index);
- Assert(BufferGetBlockNumber(buffer) == GIST_ROOT_BLKNO);
+ if (index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
+ {
+ buffer = gistNewBuffer(index);
+ Assert(BufferGetBlockNumber(buffer) == GIST_ROOT_BLKNO);
+ }
+ else
+ {
+ buffer = ReadBuffer(index, GIST_ROOT_BLKNO);
+ LockBuffer(buffer, GIST_SHARE);
+ }
page = BufferGetPage(buffer);
START_CRIT_SECTION();
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index 22d790d..5560a41 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -17,8 +17,10 @@
#include "access/genam.h"
#include "access/gist_private.h"
#include "access/relscan.h"
+#include "catalog/index.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
+#include "storage/freespace.h"
#include "storage/predicate.h"
#include "pgstat.h"
#include "lib/pairingheap.h"
@@ -344,7 +346,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)))
+ gistbuild(scan->heapRelation, r, BuildIndexInfo(r));
+ 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/hash.c b/src/backend/access/hash/hash.c
index 5cc30da..1b228db 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -119,7 +119,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* We expect to be called exactly once for any index relation. If that's
* not the case, big trouble's what we have.
*/
- if (RelationGetNumberOfBlocks(index) != 0)
+ if (RelationGetNumberOfBlocks(index) != 0 && index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
elog(ERROR, "index \"%s\" already contains data",
RelationGetRelationName(index));
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index 838ee68..544d01b 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -30,6 +30,8 @@
#include "access/hash.h"
#include "access/hash_xlog.h"
+#include "catalog/index.h"
+#include "catalog/pg_am.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "storage/smgr.h"
@@ -75,13 +77,22 @@ _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)))
+ {
+ Relation heap = RelationIdGetRelation(rel->rd_index->indrelid);
+ hashbuild(heap, rel, BuildIndexInfo(rel));
+ RelationClose(heap);
+ if (access != HASH_NOLOCK)
+ LockBuffer(buf, access);
+ }
+ else
+ {
+ if (access != HASH_NOLOCK)
+ LockBuffer(buf, access);
+ _hash_checkpage(rel, buf, flags);
+ }
return buf;
}
@@ -339,7 +350,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..eff9e10 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -27,8 +27,10 @@
#include "access/transam.h"
#include "access/xlog.h"
#include "access/xloginsert.h"
+#include "catalog/index.h"
#include "miscadmin.h"
#include "storage/indexfsm.h"
+#include "storage/buf_internals.h"
#include "storage/lmgr.h"
#include "storage/predicate.h"
#include "utils/snapmgr.h"
@@ -762,8 +764,22 @@ _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)))
+ {
+ 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);
+ }
}
else
{
diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index ab19692..227bc19 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -330,7 +330,7 @@ btbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* We expect to be called exactly once for any index relation. If that's
* not the case, big trouble's what we have.
*/
- if (RelationGetNumberOfBlocks(index) != 0)
+ if (RelationGetNumberOfBlocks(index) != 0 && index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
elog(ERROR, "index \"%s\" already contains data",
RelationGetRelationName(index));
diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/spgist/spginsert.c
index b40bd44..f44bec7 100644
--- a/src/backend/access/spgist/spginsert.c
+++ b/src/backend/access/spgist/spginsert.c
@@ -81,21 +81,32 @@ spgbuild(Relation heap, Relation index, IndexInfo *indexInfo)
rootbuffer,
nullbuffer;
- if (RelationGetNumberOfBlocks(index) != 0)
- elog(ERROR, "index \"%s\" already contains data",
- RelationGetRelationName(index));
-
- /*
- * Initialize the meta page and root pages
- */
- metabuffer = SpGistNewBuffer(index);
- rootbuffer = SpGistNewBuffer(index);
- nullbuffer = SpGistNewBuffer(index);
-
- Assert(BufferGetBlockNumber(metabuffer) == SPGIST_METAPAGE_BLKNO);
- Assert(BufferGetBlockNumber(rootbuffer) == SPGIST_ROOT_BLKNO);
- Assert(BufferGetBlockNumber(nullbuffer) == SPGIST_NULL_BLKNO);
-
+ if (index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
+ {
+ if (RelationGetNumberOfBlocks(index) != 0)
+ elog(ERROR, "index \"%s\" already contains data",
+ RelationGetRelationName(index));
+
+ /*
+ * Initialize the meta page and root pages
+ */
+ metabuffer = SpGistNewBuffer(index);
+ rootbuffer = SpGistNewBuffer(index);
+ nullbuffer = SpGistNewBuffer(index);
+
+ Assert(BufferGetBlockNumber(metabuffer) == SPGIST_METAPAGE_BLKNO);
+ Assert(BufferGetBlockNumber(rootbuffer) == SPGIST_ROOT_BLKNO);
+ Assert(BufferGetBlockNumber(nullbuffer) == SPGIST_NULL_BLKNO);
+ }
+ else
+ {
+ metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
+ rootbuffer = ReadBuffer(index, SPGIST_ROOT_BLKNO);
+ nullbuffer = ReadBuffer(index, SPGIST_NULL_BLKNO);
+ LockBuffer(metabuffer, BUFFER_LOCK_SHARE);
+ LockBuffer(rootbuffer, BUFFER_LOCK_SHARE);
+ LockBuffer(nullbuffer, BUFFER_LOCK_SHARE);
+ }
START_CRIT_SECTION();
SpGistInitMetapage(BufferGetPage(metabuffer));
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index 45472db..ea15964 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -21,6 +21,7 @@
#include "access/spgist_private.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/index.h"
#include "catalog/pg_amop.h"
#include "storage/bufmgr.h"
#include "storage/indexfsm.h"
@@ -106,6 +107,7 @@ spgGetCache(Relation index)
spgConfigIn in;
FmgrInfo *procinfo;
Buffer metabuffer;
+ Page metapage;
SpGistMetaPageData *metadata;
cache = MemoryContextAllocZero(index->rd_indexcxt,
@@ -155,12 +157,21 @@ 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))
+ {
+ Relation heap = RelationIdGetRelation(index->rd_index->indrelid);
+ spgbuild(heap, index, BuildIndexInfo(index));
+ RelationClose(heap);
+ }
+ 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/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 9fe4a47..46b07c4 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1327,7 +1327,17 @@ LANGUAGE INTERNAL
STRICT STABLE PARALLEL SAFE
AS 'jsonb_path_query_first_tz';
+
+--
+-- Statistic for global temporary tables
--
+
+CREATE VIEW pg_sequence_params AS select s.* from pg_class c,pg_sequence_parameters(c.oid) s where c.relkind='S';
+
+CREATE VIEW pg_gtt_statistic AS
+ SELECT s.* from pg_class c,pg_gtt_statistic_for_relation(c.oid) s where c.relpersistence='s';
+
+
-- The default permissions for functions mean that anyone can execute them.
-- A number of functions shouldn't be executable by just anyone, but rather
-- than use explicit 'superuser()' checks in those functions, we use the GRANT
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 7accb95..fb11f26 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -39,6 +39,7 @@
#include "commands/vacuum.h"
#include "executor/executor.h"
#include "foreign/fdwapi.h"
+#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_oper.h"
@@ -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, 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 +319,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 +577,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 +1427,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 +1529,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);
}
@@ -2859,3 +2873,114 @@ analyze_mcv_list(int *mcv_counts,
}
return num_mcv;
}
+
+PG_FUNCTION_INFO_V1(pg_gtt_statistic_for_relation);
+
+typedef struct
+{
+ int staattnum;
+ bool stainherit;
+} PgTempStatIteratorCtx;
+
+Datum
+pg_gtt_statistic_for_relation(PG_FUNCTION_ARGS)
+{
+ Oid starelid = PG_GETARG_OID(0);
+#if 1
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ TupleDesc tupdesc;
+ bool stainherit = false;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ /* Build tuplestore to hold the result rows */
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ /* Build a tuple descriptor for our result type */
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ do
+ {
+ int staattnum = 0;
+ while (true)
+ {
+ HeapTuple* statup = SearchSysCacheCopy3(STATRELATTINH,
+ ObjectIdGetDatum(starelid),
+ Int16GetDatum(++staattnum),
+ BoolGetDatum(stainherit));
+ if (statup != NULL)
+ tuplestore_puttuple(tupstore, statup);
+ else
+ break;
+ }
+ stainherit = !stainherit;
+ } while (stainherit);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+#else
+ FuncCallContext *funcctx;
+ PgTempStatIteratorCtx *it;
+ HeapTuple statup;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ MemoryContext oldcontext;
+
+ /* create a function context for cross-call persistence */
+ funcctx = SRF_FIRSTCALL_INIT();
+
+ /* switch to memory context appropriate for multiple function calls */
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+ it = palloc0(sizeof(PgTempStatIteratorCtx));
+ funcctx->user_fctx = (void *)it;
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+ else
+ {
+ funcctx = SRF_PERCALL_SETUP();
+ it = (PgTempStatIteratorCtx*)funcctx->user_fctx;
+ }
+ while (true)
+ {
+ it->staattnum += 1;
+ statup = SearchSysCacheCopy3(STATRELATTINH,
+ ObjectIdGetDatum(starelid),
+ Int16GetDatum(it->staattnum),
+ BoolGetDatum(it->stainherit));
+ if (statup != NULL)
+ SRF_RETURN_NEXT(funcctx, statup);
+
+ if (it->stainherit)
+ SRF_RETURN_DONE(funcctx);
+
+ it->stainherit = true;
+ it->staattnum = 0;
+ }
+#endif
+}
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index a23128d..b1b786d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -392,6 +392,13 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
errmsg("cannot vacuum temporary tables of other sessions")));
}
+ /* not support cluster global temp table yet */
+ if (OldHeap->rd_rel->relpersistence == RELPERSISTENCE_SESSION)
+ 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.
@@ -1400,7 +1407,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..21d5a30 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12,6 +12,9 @@
*
*-------------------------------------------------------------------------
*/
+#include <sys/stat.h>
+#include <unistd.h>
+
#include "postgres.h"
#include "access/genam.h"
@@ -533,6 +536,23 @@ static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+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;
+}
+
+
/* ----------------------------------------------------------------
* DefineRelation
* Creates a new relation.
@@ -576,6 +596,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
@@ -587,7 +608,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")));
@@ -613,17 +634,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
RangeVarGetAndCheckCreationNamespace(stmt->relation, NoLock, NULL);
/*
- * Security check: disallow creating temp tables from security-restricted
- * 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
- && InSecurityRestrictedOperation())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("cannot create temporary table within security-restricted operation")));
-
- /*
* Determine the lockmode to use when scanning parents. A self-exclusive
* lock is needed here.
*
@@ -718,6 +728,38 @@ 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_SESSION)
+ {
+ 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 if (stmt->oncommit != ONCOMMIT_NOOP)
+ {
+ 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
+ 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 +1814,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);
@@ -3449,6 +3492,26 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
(void *) stmt);
}
+
+static bool
+CheckGlobalTempTableNotInUse(Relation rel)
+{
+ int id;
+ for (id = 1; id <= MaxBackends; id++)
+ {
+ if (id != MyBackendId)
+ {
+ struct stat fst;
+ char* path = relpathbackend(rel->rd_node, id, MAIN_FORKNUM);
+ int rc = stat(path, &fst);
+ pfree(path);
+ if (rc == 0 && fst.st_size != 0)
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* AlterTable
* Execute ALTER TABLE, which can be a list of subcommands
@@ -3500,6 +3563,9 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
rel = relation_open(relid, NoLock);
CheckTableNotInUse(rel, "ALTER TABLE");
+ if (rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION
+ && !CheckGlobalTempTableNotInUse(rel))
+ elog(ERROR, "Global temp table used by active backends can not be altered");
ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode);
}
@@ -7708,6 +7774,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 +14212,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 +14706,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/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index db3a68a..60212b0 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,6 +48,7 @@
#include "partitioning/partprune.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
+#include "utils/rel.h"
/* results of subquery_is_pushdown_safe */
@@ -618,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* the rest of the necessary infrastructure right now anyway. So
* for now, bail out if we see a temporary table.
*/
- if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+ if (IsLocalRelpersistence(get_rel_persistence(rte->relid)))
return;
/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 17c5f08..7c83e7b 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,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 (RelationHasSessionScope(heap) ||
!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
{
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/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 483f705..1129dc3 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -2933,7 +2933,7 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber *forkNum,
/* If it's a local relation, it's localbuf.c's problem. */
if (RelFileNodeBackendIsTemp(rnode))
{
- if (rnode.backend == MyBackendId)
+ if (GetRelationBackendId(rnode.backend) == MyBackendId)
{
for (j = 0; j < nforks; j++)
DropRelFileNodeLocalBuffers(rnode.node, forkNum[j],
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 07f3c93..8cf06f6 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -28,17 +28,20 @@
#include "miscadmin.h"
#include "access/xlogutils.h"
#include "access/xlog.h"
+#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "pgstat.h"
#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"
#include "storage/sync.h"
#include "utils/hsearch.h"
#include "utils/memutils.h"
+#include "utils/rel.h"
#include "pg_trace.h"
/*
@@ -87,6 +90,19 @@ 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;
+ ForkNumber forknum;
+ 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 +168,60 @@ 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, rel->forknum, 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, ForkNumber forknum)
+{
+ 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->forknum = forknum;
+ rel->next = SessionRelations;
+ SessionRelations = rel;
+}
+
+static void
+RegisterOnCommitAction(SMgrRelation reln, ForkNumber forknum)
+{
+ if (reln->smgr_owner && forknum == MAIN_FORKNUM)
+ {
+ Relation rel = (Relation)((char*)reln->smgr_owner - offsetof(RelationData, rd_smgr));
+ if (rel->rd_options
+ && ((StdRdOptions *)rel->rd_options)->on_commit_delete_rows)
+ {
+ register_on_commit_action(rel->rd_id, ONCOMMIT_DELETE_ROWS);
+ }
+ }
+}
+
/*
* mdexists() -- Does the physical file exist?
*
@@ -218,6 +288,8 @@ mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo)
errmsg("could not create file \"%s\": %m", path)));
}
}
+ if (RelFileNodeBackendIsGlobalTemp(reln->smgr_rnode))
+ RegisterSessionRelation(reln, forkNum);
pfree(path);
@@ -465,6 +537,21 @@ 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, forknum);
+ if (!(behavior & EXTENSION_RETURN_NULL))
+ RegisterOnCommitAction(reln, forknum);
+ goto NewSegment;
+ }
+ }
if ((behavior & EXTENSION_RETURN_NULL) &&
FILE_POSSIBLY_DELETED(errno))
{
@@ -476,6 +563,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 +740,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),
@@ -743,7 +836,8 @@ mdnblocks(SMgrRelation reln, ForkNumber forknum)
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..720dd52 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1191,6 +1191,110 @@ 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;
+
+ /*
+ * 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/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 4688fbc..9e49d4e 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -18,6 +18,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_statistic_d.h"
#include "funcapi.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_coerce.h"
@@ -341,7 +342,8 @@ internal_get_result_type(Oid funcid,
if (resolve_polymorphic_tupdesc(tupdesc,
&procform->proargtypes,
- call_expr))
+ call_expr) ||
+ funcid == GttStatisticFunctionId)
{
if (tupdesc->tdtypeid == RECORDOID &&
tupdesc->tdtypmod < 0)
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/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 58ea5b9..082c380 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5476,7 +5476,14 @@
proname => 'pg_stat_get_xact_function_self_time', provolatile => 'v',
proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
prosrc => 'pg_stat_get_xact_function_self_time' },
-
+{ oid => '3434',
+ descr => 'show local statistics for global temp table',
+ proname => 'pg_gtt_statistic_for_relation', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', proargtypes => 'oid',
+ proallargtypes => '{oid,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,o,o,o,o,o,o,o,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,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,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_gtt_statistic_for_relation' },
{ oid => '3788',
descr => 'statistics: timestamp of the current statistics snapshot',
proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/storage/backendid.h b/src/include/storage/backendid.h
index 70ef8eb..11b4b89 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,12 @@ extern PGDLLIMPORT BackendId ParallelMasterBackendId;
#define BackendIdForTempRelations() \
(ParallelMasterBackendId == InvalidBackendId ? MyBackendId : ParallelMasterBackendId)
+
+#define BackendIdForSessionRelations() \
+ (BackendIdForTempRelations() + SessionRelFirstBackendId)
+
+#define IsSessionRelationBackendId(id) ((id) >= SessionRelFirstBackendId)
+
+#define GetRelationBackendId(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..a30137f 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
@@ -327,6 +328,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 +348,7 @@ typedef enum ViewOptCheckOption
VIEW_OPTION_CHECK_OPTION_CASCADED
} ViewOptCheckOption;
+
/*
* ViewOptions
* Contents of rd_options for views
@@ -526,7 +540,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;
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-12-01 01:56 Michael Paquier <michael@paquier.xyz>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Michael Paquier @ 2019-12-01 01:56 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Pavel Stehule <pavel.stehule@gmail.com>; Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
On Wed, Nov 20, 2019 at 07:32:14PM +0300, Konstantin Knizhnik wrote:
> Now pg_gtt_statistic view is provided for global temp tables.
Latest patch fails to apply, per Mr Robot's report. Could you please
rebase and send an updated version? For now I have moved the patch to
next CF, waiting on author.
--
Michael
Attachments:
[application/pgp-signature] signature.asc (832B, ../../20191201015629.GG2355@paquier.xyz/2-signature.asc)
download
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-12-02 09:55 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Michael Paquier <michael@paquier.xyz>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2019-12-02 09:55 UTC (permalink / raw)
To: Michael Paquier <michael@paquier.xyz>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Pavel Stehule <pavel.stehule@gmail.com>; Craig Ringer <craig@2ndquadrant.com>; pgsql-hackers
On 01.12.2019 4:56, Michael Paquier wrote:
> On Wed, Nov 20, 2019 at 07:32:14PM +0300, Konstantin Knizhnik wrote:
>> Now pg_gtt_statistic view is provided for global temp tables.
> Latest patch fails to apply, per Mr Robot's report. Could you please
> rebase and send an updated version? For now I have moved the patch to
> next CF, waiting on author.
> --
> Michael
Rebeased version of the patch is attached.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
[text/x-patch] global_private_temp-8.patch (92.0K, ../../0c7e7336-702e-583f-5427-bcb72b594c53@postgrespro.ru/2-global_private_temp-8.patch)
download | inline diff:
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 294ffa6..4e3eceb 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -672,7 +672,7 @@ brinbuild(Relation heap, Relation index, IndexInfo *indexInfo)
/*
* We expect to be called exactly once for any index relation.
*/
- if (RelationGetNumberOfBlocks(index) != 0)
+ if (RelationGetNumberOfBlocks(index) != 0 && index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
elog(ERROR, "index \"%s\" already contains data",
RelationGetRelationName(index));
@@ -681,9 +681,17 @@ brinbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* whole relation will be rolled back.
*/
- meta = ReadBuffer(index, P_NEW);
- Assert(BufferGetBlockNumber(meta) == BRIN_METAPAGE_BLKNO);
- LockBuffer(meta, BUFFER_LOCK_EXCLUSIVE);
+ if (index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
+ {
+ meta = ReadBuffer(index, P_NEW);
+ Assert(BufferGetBlockNumber(meta) == BRIN_METAPAGE_BLKNO);
+ LockBuffer(meta, BUFFER_LOCK_EXCLUSIVE);
+ }
+ else
+ {
+ meta = ReadBuffer(index, BRIN_METAPAGE_BLKNO);
+ LockBuffer(meta, BUFFER_LOCK_SHARE);
+ }
brin_metapage_init(BufferGetPage(meta), BrinGetPagesPerRange(index),
BRIN_CURRENT_VERSION);
diff --git a/src/backend/access/brin/brin_revmap.c b/src/backend/access/brin/brin_revmap.c
index 647350c..d432fec 100644
--- a/src/backend/access/brin/brin_revmap.c
+++ b/src/backend/access/brin/brin_revmap.c
@@ -25,8 +25,10 @@
#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 "catalog/index.h"
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
@@ -79,6 +81,13 @@ brinRevmapInitialize(Relation idxrel, BlockNumber *pagesPerRange,
meta = ReadBuffer(idxrel, BRIN_METAPAGE_BLKNO);
LockBuffer(meta, BUFFER_LOCK_SHARE);
page = BufferGetPage(meta);
+
+ if (GlobalTempRelationPageIsNotInitialized(idxrel, page))
+ {
+ Relation heap = RelationIdGetRelation(idxrel->rd_index->indrelid);
+ brinbuild(heap, idxrel, BuildIndexInfo(idxrel));
+ RelationClose(heap);
+ }
TestForOldSnapshot(snapshot, idxrel, page);
metadata = (BrinMetaPageData *) PageGetContents(page);
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 48377ac..3b3bbc1 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
+ },
+ false
+ },
/* 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,17 @@ 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/gin/ginfast.c b/src/backend/access/gin/ginfast.c
index 3d2153b..06c7f7a 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -22,6 +22,8 @@
#include "access/ginxlog.h"
#include "access/xlog.h"
#include "access/xloginsert.h"
+#include "commands/vacuum.h"
+#include "catalog/index.h"
#include "catalog/pg_am.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
@@ -241,6 +243,13 @@ ginHeapTupleFastInsert(GinState *ginstate, GinTupleCollector *collector)
metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
metapage = BufferGetPage(metabuffer);
+ if (GlobalTempRelationPageIsNotInitialized(index, metapage))
+ {
+ Relation heap = RelationIdGetRelation(index->rd_index->indrelid);
+ ginbuild(heap, index, BuildIndexInfo(index));
+ RelationClose(heap);
+ }
+
/*
* 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..2e447be 100644
--- a/src/backend/access/gin/ginget.c
+++ b/src/backend/access/gin/ginget.c
@@ -16,6 +16,7 @@
#include "access/gin_private.h"
#include "access/relscan.h"
+#include "catalog/index.h"
#include "miscadmin.h"
#include "storage/predicate.h"
#include "utils/datum.h"
@@ -1759,7 +1760,8 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
match;
int i;
pendingPosition pos;
- Buffer metabuffer = ReadBuffer(scan->indexRelation, GIN_METAPAGE_BLKNO);
+ Relation index = scan->indexRelation;
+ Buffer metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
Page page;
BlockNumber blkno;
@@ -1769,11 +1771,19 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
* Acquire predicate lock on the metapage, to conflict with any fastupdate
* insertions.
*/
- PredicateLockPage(scan->indexRelation, GIN_METAPAGE_BLKNO, scan->xs_snapshot);
+ PredicateLockPage(index, GIN_METAPAGE_BLKNO, scan->xs_snapshot);
LockBuffer(metabuffer, GIN_SHARE);
page = BufferGetPage(metabuffer);
- TestForOldSnapshot(scan->xs_snapshot, scan->indexRelation, page);
+ TestForOldSnapshot(scan->xs_snapshot, index, page);
+
+ if (GlobalTempRelationPageIsNotInitialized(index, page))
+ {
+ Relation heap = RelationIdGetRelation(index->rd_index->indrelid);
+ ginbuild(heap, index, BuildIndexInfo(index));
+ RelationClose(heap);
+ UnlockReleaseBuffer(metabuffer);
+ }
blkno = GinPageGetMeta(page)->head;
/*
@@ -1787,7 +1797,7 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
return;
}
- pos.pendingBuffer = ReadBuffer(scan->indexRelation, blkno);
+ pos.pendingBuffer = ReadBuffer(index, blkno);
LockBuffer(pos.pendingBuffer, GIN_SHARE);
pos.firstOffset = FirstOffsetNumber;
UnlockReleaseBuffer(metabuffer);
diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c
index 1ad6228..49d245c 100644
--- a/src/backend/access/gin/gininsert.c
+++ b/src/backend/access/gin/gininsert.c
@@ -329,7 +329,7 @@ ginbuild(Relation heap, Relation index, IndexInfo *indexInfo)
MemoryContext oldCtx;
OffsetNumber attnum;
- if (RelationGetNumberOfBlocks(index) != 0)
+ if (RelationGetNumberOfBlocks(index) != 0 && index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
elog(ERROR, "index \"%s\" already contains data",
RelationGetRelationName(index));
@@ -338,7 +338,15 @@ ginbuild(Relation heap, Relation index, IndexInfo *indexInfo)
memset(&buildstate.buildStats, 0, sizeof(GinStatsData));
/* initialize the meta page */
- MetaBuffer = GinNewBuffer(index);
+ if (index->rd_rel->relpersistence == RELPERSISTENCE_SESSION)
+ {
+ MetaBuffer = ReadBuffer(index, 0);
+ LockBuffer(MetaBuffer, GIN_SHARE);
+ }
+ else
+ {
+ MetaBuffer = GinNewBuffer(index);
+ }
/* initialize the root page */
RootBuffer = GinNewBuffer(index);
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 3800f58..e9d1218 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -16,6 +16,7 @@
#include "access/gist_private.h"
#include "access/gistscan.h"
+#include "catalog/index.h"
#include "catalog/pg_collation.h"
#include "miscadmin.h"
#include "nodes/execnodes.h"
@@ -676,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)))
+ gistbuild(heapRel, r, BuildIndexInfo(r));
+ else
+ gistcheckpage(state.r, stack->buffer);
}
stack->page = (Page) BufferGetPage(stack->buffer);
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 739846a..f35072b 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -156,7 +156,7 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* We expect to be called exactly once for any index relation. If that's
* not the case, big trouble's what we have.
*/
- if (RelationGetNumberOfBlocks(index) != 0)
+ if (RelationGetNumberOfBlocks(index) != 0 && index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
elog(ERROR, "index \"%s\" already contains data",
RelationGetRelationName(index));
@@ -171,8 +171,16 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
buildstate.giststate->tempCxt = createTempGistContext();
/* initialize the root page */
- buffer = gistNewBuffer(index);
- Assert(BufferGetBlockNumber(buffer) == GIST_ROOT_BLKNO);
+ if (index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
+ {
+ buffer = gistNewBuffer(index);
+ Assert(BufferGetBlockNumber(buffer) == GIST_ROOT_BLKNO);
+ }
+ else
+ {
+ buffer = ReadBuffer(index, GIST_ROOT_BLKNO);
+ LockBuffer(buffer, GIST_SHARE);
+ }
page = BufferGetPage(buffer);
START_CRIT_SECTION();
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index 98b6892..90d4eaa 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -17,10 +17,12 @@
#include "access/genam.h"
#include "access/gist_private.h"
#include "access/relscan.h"
+#include "catalog/index.h"
#include "lib/pairingheap.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/lmgr.h"
+#include "storage/freespace.h"
#include "storage/predicate.h"
#include "utils/float.h"
#include "utils/memutils.h"
@@ -344,7 +346,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)))
+ gistbuild(scan->heapRelation, r, BuildIndexInfo(r));
+ 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 553a6d6..581f4c2 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,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/hash.c b/src/backend/access/hash/hash.c
index a0597a0..56bd161 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -117,7 +117,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* We expect to be called exactly once for any index relation. If that's
* not the case, big trouble's what we have.
*/
- if (RelationGetNumberOfBlocks(index) != 0)
+ if (RelationGetNumberOfBlocks(index) != 0 && index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
elog(ERROR, "index \"%s\" already contains data",
RelationGetRelationName(index));
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index f84cee8..1948ef7 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -30,6 +30,8 @@
#include "access/hash.h"
#include "access/hash_xlog.h"
+#include "catalog/index.h"
+#include "catalog/pg_am.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "storage/predicate.h"
@@ -74,13 +76,22 @@ _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)))
+ {
+ Relation heap = RelationIdGetRelation(rel->rd_index->indrelid);
+ hashbuild(heap, rel, BuildIndexInfo(rel));
+ RelationClose(heap);
+ if (access != HASH_NOLOCK)
+ LockBuffer(buf, access);
+ }
+ else
+ {
+ if (access != HASH_NOLOCK)
+ LockBuffer(buf, access);
+ _hash_checkpage(rel, buf, flags);
+ }
return buf;
}
@@ -338,7 +349,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 92073fe..54533a3 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -670,6 +670,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..eff9e10 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -27,8 +27,10 @@
#include "access/transam.h"
#include "access/xlog.h"
#include "access/xloginsert.h"
+#include "catalog/index.h"
#include "miscadmin.h"
#include "storage/indexfsm.h"
+#include "storage/buf_internals.h"
#include "storage/lmgr.h"
#include "storage/predicate.h"
#include "utils/snapmgr.h"
@@ -762,8 +764,22 @@ _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)))
+ {
+ 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);
+ }
}
else
{
diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index 1dd39a9..785b665 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -321,7 +321,7 @@ btbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* We expect to be called exactly once for any index relation. If that's
* not the case, big trouble's what we have.
*/
- if (RelationGetNumberOfBlocks(index) != 0)
+ if (RelationGetNumberOfBlocks(index) != 0 && index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
elog(ERROR, "index \"%s\" already contains data",
RelationGetRelationName(index));
diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/spgist/spginsert.c
index dd90887..305f171 100644
--- a/src/backend/access/spgist/spginsert.c
+++ b/src/backend/access/spgist/spginsert.c
@@ -81,21 +81,32 @@ spgbuild(Relation heap, Relation index, IndexInfo *indexInfo)
rootbuffer,
nullbuffer;
- if (RelationGetNumberOfBlocks(index) != 0)
- elog(ERROR, "index \"%s\" already contains data",
- RelationGetRelationName(index));
-
- /*
- * Initialize the meta page and root pages
- */
- metabuffer = SpGistNewBuffer(index);
- rootbuffer = SpGistNewBuffer(index);
- nullbuffer = SpGistNewBuffer(index);
-
- Assert(BufferGetBlockNumber(metabuffer) == SPGIST_METAPAGE_BLKNO);
- Assert(BufferGetBlockNumber(rootbuffer) == SPGIST_ROOT_BLKNO);
- Assert(BufferGetBlockNumber(nullbuffer) == SPGIST_NULL_BLKNO);
-
+ if (index->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
+ {
+ if (RelationGetNumberOfBlocks(index) != 0)
+ elog(ERROR, "index \"%s\" already contains data",
+ RelationGetRelationName(index));
+
+ /*
+ * Initialize the meta page and root pages
+ */
+ metabuffer = SpGistNewBuffer(index);
+ rootbuffer = SpGistNewBuffer(index);
+ nullbuffer = SpGistNewBuffer(index);
+
+ Assert(BufferGetBlockNumber(metabuffer) == SPGIST_METAPAGE_BLKNO);
+ Assert(BufferGetBlockNumber(rootbuffer) == SPGIST_ROOT_BLKNO);
+ Assert(BufferGetBlockNumber(nullbuffer) == SPGIST_NULL_BLKNO);
+ }
+ else
+ {
+ metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
+ rootbuffer = ReadBuffer(index, SPGIST_ROOT_BLKNO);
+ nullbuffer = ReadBuffer(index, SPGIST_NULL_BLKNO);
+ LockBuffer(metabuffer, BUFFER_LOCK_SHARE);
+ LockBuffer(rootbuffer, BUFFER_LOCK_SHARE);
+ LockBuffer(nullbuffer, BUFFER_LOCK_SHARE);
+ }
START_CRIT_SECTION();
SpGistInitMetapage(BufferGetPage(metabuffer));
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index e2d391e..42137de 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -21,6 +21,7 @@
#include "access/spgist_private.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/index.h"
#include "catalog/pg_amop.h"
#include "storage/bufmgr.h"
#include "storage/indexfsm.h"
@@ -106,6 +107,7 @@ spgGetCache(Relation index)
spgConfigIn in;
FmgrInfo *procinfo;
Buffer metabuffer;
+ Page metapage;
SpGistMetaPageData *metadata;
cache = MemoryContextAllocZero(index->rd_indexcxt,
@@ -155,12 +157,21 @@ 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))
+ {
+ Relation heap = RelationIdGetRelation(index->rd_index->indrelid);
+ spgbuild(heap, index, BuildIndexInfo(index));
+ RelationClose(heap);
+ }
+ 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 6b10469..88fca55 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -401,6 +401,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 e995570..1522109 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3699,7 +3699,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 056ea3d..317574a 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -92,6 +92,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/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index f7800f0..23ab456 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1332,7 +1332,15 @@ LANGUAGE INTERNAL
STRICT STABLE PARALLEL SAFE
AS 'jsonb_path_query_first_tz';
+
+--
+-- Statistic for global temporary tables
--
+
+CREATE VIEW pg_gtt_statistic AS
+ SELECT s.* from pg_class c,pg_gtt_statistic_for_relation(c.oid) s where c.relpersistence='s';
+
+
-- The default permissions for functions mean that anyone can execute them.
-- A number of functions shouldn't be executable by just anyone, but rather
-- than use explicit 'superuser()' checks in those functions, we use the GRANT
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 71372ce..324a249 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -39,6 +39,7 @@
#include "commands/vacuum.h"
#include "executor/executor.h"
#include "foreign/fdwapi.h"
+#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_oper.h"
@@ -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, 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 +319,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 +577,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 +1427,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 +1529,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);
}
@@ -2859,3 +2873,72 @@ analyze_mcv_list(int *mcv_counts,
}
return num_mcv;
}
+
+PG_FUNCTION_INFO_V1(pg_gtt_statistic_for_relation);
+
+typedef struct
+{
+ int staattnum;
+ bool stainherit;
+} PgTempStatIteratorCtx;
+
+Datum
+pg_gtt_statistic_for_relation(PG_FUNCTION_ARGS)
+{
+ Oid starelid = PG_GETARG_OID(0);
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ TupleDesc tupdesc;
+ bool stainherit = false;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ /* Build tuplestore to hold the result rows */
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ /* Build a tuple descriptor for our result type */
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ do
+ {
+ int staattnum = 0;
+ while (true)
+ {
+ HeapTuple statup = SearchSysCacheCopy3(STATRELATTINH,
+ ObjectIdGetDatum(starelid),
+ Int16GetDatum(++staattnum),
+ BoolGetDatum(stainherit));
+ if (statup != NULL)
+ tuplestore_puttuple(tupstore, statup);
+ else
+ break;
+ }
+ stainherit = !stainherit;
+ } while (stainherit);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b8c349f..80b7fb4 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,13 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
errmsg("cannot vacuum temporary tables of other sessions")));
}
+ /* not support cluster global temp table yet */
+ if (OldHeap->rd_rel->relpersistence == RELPERSISTENCE_SESSION)
+ 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.
@@ -1399,7 +1406,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 5440eb9..6aec01d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12,6 +12,9 @@
*
*-------------------------------------------------------------------------
*/
+#include <sys/stat.h>
+#include <unistd.h>
+
#include "postgres.h"
#include "access/genam.h"
@@ -531,6 +534,23 @@ static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+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;
+}
+
+
/* ----------------------------------------------------------------
* DefineRelation
* Creates a new relation.
@@ -574,6 +594,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,7 +606,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")));
@@ -611,17 +632,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
RangeVarGetAndCheckCreationNamespace(stmt->relation, NoLock, NULL);
/*
- * Security check: disallow creating temp tables from security-restricted
- * 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
- && InSecurityRestrictedOperation())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("cannot create temporary table within security-restricted operation")));
-
- /*
* Determine the lockmode to use when scanning parents. A self-exclusive
* lock is needed here.
*
@@ -716,6 +726,38 @@ 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_SESSION)
+ {
+ 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 if (stmt->oncommit != ONCOMMIT_NOOP)
+ {
+ 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
+ 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);
@@ -1777,7 +1819,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);
@@ -3456,6 +3499,26 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
(void *) stmt);
}
+
+static bool
+CheckGlobalTempTableNotInUse(Relation rel)
+{
+ int id;
+ for (id = 1; id <= MaxBackends; id++)
+ {
+ if (id != MyBackendId)
+ {
+ struct stat fst;
+ char* path = relpathbackend(rel->rd_node, id, MAIN_FORKNUM);
+ int rc = stat(path, &fst);
+ pfree(path);
+ if (rc == 0 && fst.st_size != 0)
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* AlterTable
* Execute ALTER TABLE, which can be a list of subcommands
@@ -3507,6 +3570,9 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
rel = relation_open(relid, NoLock);
CheckTableNotInUse(rel, "ALTER TABLE");
+ if (rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION
+ && !CheckGlobalTempTableNotInUse(rel))
+ elog(ERROR, "Global temp table used by active backends can not be altered");
ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode);
}
@@ -7715,6 +7781,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,
@@ -14149,6 +14221,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 */
@@ -14636,14 +14715,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/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index db3a68a..60212b0 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,6 +48,7 @@
#include "partitioning/partprune.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
+#include "utils/rel.h"
/* results of subquery_is_pushdown_safe */
@@ -618,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* the rest of the necessary infrastructure right now anyway. So
* for now, bail out if we see a temporary table.
*/
- if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+ if (IsLocalRelpersistence(get_rel_persistence(rte->relid)))
return;
/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 7fe11b5..4f99402 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6306,7 +6306,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 (RelationHasSessionScope(heap) ||
!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
{
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c508684..c9e228b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3268,20 +3268,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/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 7ad1073..a085893 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -2933,7 +2933,7 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber *forkNum,
/* If it's a local relation, it's localbuf.c's problem. */
if (RelFileNodeBackendIsTemp(rnode))
{
- if (rnode.backend == MyBackendId)
+ if (GetRelationBackendId(rnode.backend) == MyBackendId)
{
for (j = 0; j < nforks; j++)
DropRelFileNodeLocalBuffers(rnode.node, forkNum[j],
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 8a9eaf6..997d331 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -27,12 +27,14 @@
#include "access/xlog.h"
#include "access/xlogutils.h"
+#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "miscadmin.h"
#include "pg_trace.h"
#include "pgstat.h"
#include "postmaster/bgwriter.h"
#include "storage/bufmgr.h"
+#include "storage/ipc.h"
#include "storage/fd.h"
#include "storage/md.h"
#include "storage/relfilenode.h"
@@ -40,6 +42,7 @@
#include "storage/sync.h"
#include "utils/hsearch.h"
#include "utils/memutils.h"
+#include "utils/rel.h"
/*
* The magnetic disk storage manager keeps track of open file
@@ -87,6 +90,19 @@ 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;
+ ForkNumber forknum;
+ 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 +168,60 @@ 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, rel->forknum, 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, ForkNumber forknum)
+{
+ 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->forknum = forknum;
+ rel->next = SessionRelations;
+ SessionRelations = rel;
+}
+
+static void
+RegisterOnCommitAction(SMgrRelation reln, ForkNumber forknum)
+{
+ if (reln->smgr_owner && forknum == MAIN_FORKNUM)
+ {
+ Relation rel = (Relation)((char*)reln->smgr_owner - offsetof(RelationData, rd_smgr));
+ if (rel->rd_options
+ && ((StdRdOptions *)rel->rd_options)->on_commit_delete_rows)
+ {
+ register_on_commit_action(rel->rd_id, ONCOMMIT_DELETE_ROWS);
+ }
+ }
+}
+
/*
* mdexists() -- Does the physical file exist?
*
@@ -218,6 +288,8 @@ mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo)
errmsg("could not create file \"%s\": %m", path)));
}
}
+ if (RelFileNodeBackendIsGlobalTemp(reln->smgr_rnode))
+ RegisterSessionRelation(reln, forkNum);
pfree(path);
@@ -465,6 +537,21 @@ 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, forknum);
+ if (!(behavior & EXTENSION_RETURN_NULL))
+ RegisterOnCommitAction(reln, forknum);
+ goto NewSegment;
+ }
+ }
if ((behavior & EXTENSION_RETURN_NULL) &&
FILE_POSSIBLY_DELETED(errno))
{
@@ -476,6 +563,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 +740,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),
@@ -743,7 +836,8 @@ mdnblocks(SMgrRelation reln, ForkNumber forknum)
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..720dd52 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1191,6 +1191,110 @@ 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;
+
+ /*
+ * 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 50f8912..d9e0230 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1097,6 +1097,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;
@@ -3300,6 +3304,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 d69c0ff..87d273e 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -1156,6 +1156,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/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 4688fbc..e191511 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -18,6 +18,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_statistic_d.h"
#include "funcapi.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_coerce.h"
@@ -30,6 +31,13 @@
#include "utils/syscache.h"
#include "utils/typcache.h"
+/*
+ * TODO: find less ugly way to declare core function returning pg_statistics.
+ * OID of pg_gtt_statistic_for_relation. This function should be handled in special way because it returns set of pg_statistics
+ * which contains attributes of anyarray type. Type of attributes can not be deduced from input parameters and
+ * it prevents using tuple descriptor in this case.
+ */
+#define GttStatisticFunctionId 3434
static void shutdown_MultiFuncCall(Datum arg);
static TypeFuncClass internal_get_result_type(Oid funcid,
@@ -341,7 +349,8 @@ internal_get_result_type(Oid funcid,
if (resolve_polymorphic_tupdesc(tupdesc,
&procform->proargtypes,
- call_expr))
+ call_expr) ||
+ funcid == GttStatisticFunctionId)
{
if (tupdesc->tdtypeid == RECORDOID &&
tupdesc->tdtypmod < 0)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 08658c8..f2adc5a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15635,8 +15635,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/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index ac8f64b..a683ef0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5476,7 +5476,14 @@
proname => 'pg_stat_get_xact_function_self_time', provolatile => 'v',
proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
prosrc => 'pg_stat_get_xact_function_self_time' },
-
+{ oid => '3434',
+ descr => 'show local statistics for global temp table',
+ proname => 'pg_gtt_statistic_for_relation', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '100', proargtypes => 'oid',
+ proallargtypes => '{oid,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,o,o,o,o,o,o,o,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,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,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_gtt_statistic_for_relation' },
{ oid => '3788',
descr => 'statistics: timestamp of the current statistics snapshot',
proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/storage/backendid.h b/src/include/storage/backendid.h
index 70ef8eb..11b4b89 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,12 @@ extern PGDLLIMPORT BackendId ParallelMasterBackendId;
#define BackendIdForTempRelations() \
(ParallelMasterBackendId == InvalidBackendId ? MyBackendId : ParallelMasterBackendId)
+
+#define BackendIdForSessionRelations() \
+ (BackendIdForTempRelations() + SessionRelFirstBackendId)
+
+#define IsSessionRelationBackendId(id) ((id) >= SessionRelFirstBackendId)
+
+#define GetRelationBackendId(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 31d8a1a..171717e 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -271,6 +271,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
@@ -326,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
{
@@ -334,6 +347,7 @@ typedef enum ViewOptCheckOption
VIEW_OPTION_CHECK_OPTION_CASCADED
} ViewOptCheckOption;
+
/*
* ViewOptions
* Contents of rd_options for views
@@ -529,7 +543,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/rules.out b/src/test/regress/expected/rules.out
index c9cc569..8c21b00 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1348,6 +1348,40 @@ 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_statistic| SELECT s.starelid,
+ s.staattnum,
+ s.stainherit,
+ s.stanullfrac,
+ s.stawidth,
+ s.stadistinct,
+ s.stakind1,
+ s.stakind2,
+ s.stakind3,
+ s.stakind4,
+ s.stakind5,
+ s.staop1,
+ s.staop2,
+ s.staop3,
+ s.staop4,
+ s.staop5,
+ s.stacoll1,
+ s.stacoll2,
+ s.stacoll3,
+ s.stacoll4,
+ s.stacoll5,
+ s.stanumbers1,
+ s.stanumbers2,
+ s.stanumbers3,
+ s.stanumbers4,
+ s.stanumbers5,
+ s.stavalues1,
+ s.stavalues2,
+ s.stavalues3,
+ s.stavalues4,
+ s.stavalues5
+ FROM pg_class c,
+ LATERAL pg_gtt_statistic_for_relation(c.oid) 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 = 's'::"char");
pg_hba_file_rules| SELECT a.line_number,
a.type,
a.database,
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 d33a4e1..bda6854 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 f86f5c5..f94b32b 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;
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2019-12-22 17:04 Philippe BEAUDOIN <phb07@apra.asso.fr>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Philippe BEAUDOIN @ 2019-12-22 17:04 UTC (permalink / raw)
To: pgsql-hackers@lists.postgresql.org; +Cc: Konstantin Knizhnik <knizhnik@garret.ru>
Hi all,
I am not aware enough in the Postgres internals to give advice about the implementation.
But my feeling is that there is another big interest for this feature: simplify the Oracle to PostgreSQL migration of applications that use global termporary tables. And this is quite common when stored procedures are used. In such a case, we currently need to modify the logic of the code, always implementing an ugly solution (either add CREATE TEMP TABLE statements in the code everywhere it is needed, or use a regular table with additional TRUNCATE statements if we can ensure that only a single connection uses the table at a time).
So, Konstantin and all, Thanks by advance for all that could be done on this feature :-)
Best regards.
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-05 20:06 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 2 replies; 525+ messages in thread
From: Tomas Vondra @ 2020-01-05 20:06 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-06 05:04 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
1 sibling, 2 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-01-06 05:04 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
> 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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-06 11:01 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
1 sibling, 2 replies; 525+ messages in thread
From: Tomas Vondra @ 2020-01-06 11:01 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-06 12:17 Dean Rasheed <dean.a.rasheed@gmail.com>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
1 sibling, 3 replies; 525+ messages in thread
From: Dean Rasheed @ 2020-01-06 12:17 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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.
Regards,
Dean
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-06 12:52 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Dean Rasheed <dean.a.rasheed@gmail.com>
2 siblings, 0 replies; 525+ messages in thread
From: Tomas Vondra @ 2020-01-06 12:52 UTC (permalink / raw)
To: Dean Rasheed <dean.a.rasheed@gmail.com>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-06 13:50 Pavel Stehule <pavel.stehule@gmail.com>
parent: Dean Rasheed <dean.a.rasheed@gmail.com>
2 siblings, 0 replies; 525+ messages in thread
From: Pavel Stehule @ 2020-01-06 13:50 UTC (permalink / raw)
To: Dean Rasheed <dean.a.rasheed@gmail.com>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
> Regards,
> Dean
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-08 07:03 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Dean Rasheed <dean.a.rasheed@gmail.com>
2 siblings, 0 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-01-08 07:03 UTC (permalink / raw)
To: Dean Rasheed <dean.a.rasheed@gmail.com>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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.
> Regards,
> Dean
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-09 11:17 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
1 sibling, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-01-09 11:17 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On 06.01.2020 8:04, 曾文旌(义从) wrote:
> In the previous communication
>
> 1 we agreed on the general direction
> 1.1 gtt use local (private) buffer
> 1.2 no replica access in first version
>
> 2 We feel that gtt needs to maintain statistics, but there is no agreement on what it will be done.
>
> 3 Still no one commented on GTT's transaction information processing, they include
> 3.1 Should gtt's frozenxid need to be care?
> 3.2 gtt’s clog clean
> 3.3 How to deal with "too old" gtt data
>
> I suggest we discuss further, reach an agreement, and merge the two patches to one.
>
I also hope that we should come to the common solution for GTT.
If we do not try to address parallel execution issues and access to temp
tables at replicas (and I agreed
that it should be avoided in first version of the patch), then GTT patch
becomes quite small.
The most complex and challenged task is to support GTT for all kind of
indexes. Unfortunately I can not proposed some good universal solution
for it.
Just patching all existed indexes implementation seems to be the only
choice.
Statistic is another important case.
But once again I do not completely understand why we want to address all
this issues with statistic in first version of the patch? It contradicts
to the idea to make this patch as small as possible.
Also it seems to me that everybody agreed that users very rarely create
indexes for temp tables and explicitly analyze them.
So I think GTT will be useful even with limited support of statistic. In
my version statistics for GTT is provided by pushing correspondent
information to backend's cache for pg_statistic table.
Also I provided pg_temp_statistic view for inspecting it by users. The
idea to make pg_statistic a view which combines statistic of normal and
temporary tables is overkill from my point of view.
I do not understand why do we need to maintain hash with some extra
information for GTT in backends memory (as it was done in Wenjing patch).
Also idea to use create extension for accessing this information seems
to be dubious.
--
Konstantin Knizhnik
Postgres Professional:http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-09 15:07 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
1 sibling, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-01-09 15:07 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On 06.01.2020 14:01, Tomas Vondra wrote:
> On Mon, Jan 06, 2020 at 01:04:15PM +0800, 曾文旌(义从) wrote:
>> In the previous communication
>>
>> 1 we agreed on the general direction
>> 1.1 gtt use local (private) buffer
>> 1.2 no replica access in first version
>>
>
> OK, good.
>
>> 2 We feel that gtt needs to maintain statistics, but there is no
>> agreement on what it will be done.
>>
>
> I certainly agree GTT needs to maintain statistics, otherwise it'll lead
> to poor query plans. AFAIK the current patch stores the info in a hash
> table in a backend private memory, and I don't see how else to do that
> (e.g. storing it in a catalog would cause catalog bloat).
>
> FWIW this is a reasons why I think just using shared buffers (instead of
> local ones) is not sufficient to support parallel queriesl as proposed
> by Alexander. The workers would not know the stats, breaking planning of
> queries in PARALLEL SAFE plpgsql functions etc.
I do not think that "all or nothing" approach is so good for software
development as for database transactions.
Yes, if we have function in PL/pgSQL which performs queries om temporary
tables, then
parallel workers may build inefficient plan for this queries due to lack
of statistics.
From my point of view this is not a pitfall of GTT but result of lack
of global plan cache in Postgres. And it should be fixed not at GTT level.
Also I never see real use cases with such functions, even in the systems
which using hard temporary tables and stored procedures.
But there are many other real problems with temp tables (except already
mentioned in this thread).
In PgPro/EE we have fixes for some of them, for example:
1. Do not reserve space in the file for temp relations. Right now append
of relation cause writing zero page to the disk by mdextend.
It cause useless disk IO for temp tables which in most cases fit in
memory and should not be written at disk.
2. Implicitly perform analyze of temp table intermediately after storing
data in it. Usually tables are analyzed by autovacuum in background.
But it doesn't work for temp tables which are not processes by
autovacuum and are accessed immediately after filling them with data and
lack of statistic may cause
building very inefficient plan. We have online_analyze extension which
force analyze of the table after appending some bulk of data to it.
It can be used for normal table but most of all it is useful for temp
relations.
Unlike hypothetical example with parallel safe function working with
temp tables,
this are real problems observed by some of our customers.
Them are applicable both to local and global temp tables and this is why
I do not want to discuss them in context of GTT.
>
>> 3 Still no one commented on GTT's transaction information processing,
>> they include
>> 3.1 Should gtt's frozenxid need to be care?
>> 3.2 gtt’s clog clean
>> 3.3 How to deal with "too old" gtt data
>>
>
> No idea what to do about this.
>
I wonder what is the specific of GTT here?
The same problem takes place for normal (local) temp tables, doesn't it?
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-09 16:30 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Tomas Vondra @ 2020-01-09 16:30 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On Thu, Jan 09, 2020 at 06:07:46PM +0300, Konstantin Knizhnik wrote:
>
>
>On 06.01.2020 14:01, Tomas Vondra wrote:
>>On Mon, Jan 06, 2020 at 01:04:15PM +0800, 曾文旌(义从) wrote:
>>>In the previous communication
>>>
>>>1 we agreed on the general direction 1.1 gtt use local (private)
>>>buffer 1.2 no replica access in first version
>>>
>>
>>OK, good.
>>
>>>2 We feel that gtt needs to maintain statistics, but there is no
>>>agreement on what it will be done.
>>>
>>
>>I certainly agree GTT needs to maintain statistics, otherwise it'll
>>lead to poor query plans. AFAIK the current patch stores the info in a
>>hash table in a backend private memory, and I don't see how else to do
>>that (e.g. storing it in a catalog would cause catalog bloat).
>>
>>FWIW this is a reasons why I think just using shared buffers (instead
>>of local ones) is not sufficient to support parallel queriesl as
>>proposed by Alexander. The workers would not know the stats, breaking
>>planning of queries in PARALLEL SAFE plpgsql functions etc.
>
>
>I do not think that "all or nothing" approach is so good for software
>development as for database transactions.
Well, sure. I'm not saying we need to have a perfect solution in v1. I'm
saying if we have two choices:
(1) Use shared buffers even if it means the parallel query plan may be
arbitrarily bad.
(2) Use private buffers, even if it means no parallel queries with temp
tables.
Then I'm voting for (2) because it's less likely to break down. I can
imagine allowing parallel queries with GTT when there's no risk of
having to plan in the worker, but that's not there yet.
If we can come up with a reasonable solution for the parallel case, we
can enable it later.
>Yes, if we have function in PL/pgSQL which performs queries om
>temporary tables, then
>parallel workers may build inefficient plan for this queries due to
>lack of statistics.
IMHO that's a pretty awful deficiency, because it essentially means
users may need to disable parallelism for such queries. Which means
we'll get complaints from users, and we'll have to come up with some
sort of solution. I'd rather not be in that position.
From my point of view this is not a pitfall of GTT but result of lack
>of global plan cache in Postgres. And it should be fixed not at GTT
>level.
>
That doesn't give us free pass to just ignore the issue. Even if it
really was due to a lack of global plan cache, the fact is we don't have
that feature, so we have a problem. I mean, if you need infrastructure
that is not available, you either have to implement that infrastructure
or make it work properly without it.
>Also I never see real use cases with such functions, even in the
>systems which using hard temporary tables and stored procedures.
>But there are many other real problems with temp tables (except
>already mentioned in this thread).
Oh, I'm sure there are pretty large plpgsql applications, and I'd be
surprised if at least some of those were not affected. And I'm sure
there are apps using UDF to do all sorts of stuff (e.g. I wonder if
PostGIS would have this issue - IIRC it's using SPI etc.).
The question is whether we should consider existing apps affected,
because they are using the regular temporary tables and not GTT. So
unless they switch to GTT there is no regression ...
But even in that case I don't think it's a good idea to accept this as
an acceptable limitation. I admit one of the reasons why I think that
may be that statistics and planning are my areas of interest, so I'm not
quite willing to accept incomplete stuff as OK.
>In PgPro/EE we have fixes for some of them, for example:
>
>1. Do not reserve space in the file for temp relations. Right now
>append of relation cause writing zero page to the disk by mdextend.
>It cause useless disk IO for temp tables which in most cases fit in
>memory and should not be written at disk.
>
>2. Implicitly perform analyze of temp table intermediately after
>storing data in it. Usually tables are analyzed by autovacuum in
>background.
>But it doesn't work for temp tables which are not processes by
>autovacuum and are accessed immediately after filling them with data
>and lack of statistic may cause
>building very inefficient plan. We have online_analyze extension which
>force analyze of the table after appending some bulk of data to it.
>It can be used for normal table but most of all it is useful for temp
>relations.
>
>Unlike hypothetical example with parallel safe function working with
>temp tables,
>this are real problems observed by some of our customers.
>Them are applicable both to local and global temp tables and this is
>why I do not want to discuss them in context of GTT.
>
I think those are both interesting issues worth fixing, but I don't
think it makes the issue discussed here less important.
>
>>
>>>3 Still no one commented on GTT's transaction information
>>>processing, they include
>>>3.1 Should gtt's frozenxid need to be care?
>>>3.2 gtt’s clog clean
>>>3.3 How to deal with "too old" gtt data
>>>
>>
>>No idea what to do about this.
>>
>
>I wonder what is the specific of GTT here?
>The same problem takes place for normal (local) temp tables, doesn't it?
>
Not sure. TBH I'm not sure I understand what the issue actually is.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-09 16:48 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Tomas Vondra @ 2020-01-09 16:48 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On Thu, Jan 09, 2020 at 02:17:08PM +0300, Konstantin Knizhnik wrote:
>
>
>On 06.01.2020 8:04, 曾文旌(义从) wrote:
>>In the previous communication
>>
>>1 we agreed on the general direction
>>1.1 gtt use local (private) buffer
>>1.2 no replica access in first version
>>
>>2 We feel that gtt needs to maintain statistics, but there is no agreement on what it will be done.
>>
>>3 Still no one commented on GTT's transaction information processing, they include
>>3.1 Should gtt's frozenxid need to be care?
>>3.2 gtt’s clog clean
>>3.3 How to deal with "too old" gtt data
>>
>>I suggest we discuss further, reach an agreement, and merge the two patches to one.
>>
>
>I also hope that we should come to the common solution for GTT.
>If we do not try to address parallel execution issues and access to
>temp tables at replicas (and I agreed
>that it should be avoided in first version of the patch), then GTT
>patch becomes quite small.
>
Well, that was kinda my goal - making the patch as small as possible by
eliminating bits that are contentious or where we don't know the
solution (like planning for parallel queries).
>The most complex and challenged task is to support GTT for all kind of
>indexes. Unfortunately I can not proposed some good universal solution
>for it.
>Just patching all existed indexes implementation seems to be the only
>choice.
>
I haven't looked at the indexing issue closely, but IMO we need to
ensure that every session sees/uses only indexes on GTT that were
defined before the seesion started using the table.
Can't we track which indexes a particular session sees, somehow?
>Statistic is another important case.
>But once again I do not completely understand why we want to address
>all this issues with statistic in first version of the patch?
I think the question is which "issues with statistic" you mean. I'm sure
we can ignore some of them, e.g. the one with parallel workers not
having any stats (assuming we consider functions using GTT to be
parallel restricted).
>It contradicts to the idea to make this patch as small as possible.
Well, there's "making patch as small as possible" vs. "patch behaving
correctly" trade-off ;-)
>Also it seems to me that everybody agreed that users very rarely
>create indexes for temp tables and explicitly analyze them.
I certainly *disagree* with this.
We often see temporary tables as a fix or misestimates in complex
queries, and/or as a replacement for CTEs with statistics/indexes. In
fact it's a pretty valuable tool when helping customers with complex
queries affected by poor estimates.
>So I think GTT will be useful even with limited support of statistic.
>In my version statistics for GTT is provided by pushing correspondent
>information to backend's cache for pg_statistic table.
I think someone pointed out pushing stuff directly into the cache is
rather problematic, but I don't recall the details.
>Also I provided pg_temp_statistic view for inspecting it by users. The
>idea to make pg_statistic a view which combines statistic of normal
>and temporary tables is overkill from my point of view.
>
>I do not understand why do we need to maintain hash with some extra
>information for GTT in backends memory (as it was done in Wenjing
>patch).
>Also idea to use create extension for accessing this information seems
>to be dubious.
>
I think the extension was more a PoC rather than a final solution.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-10 08:47 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-01-10 08:47 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-10 12:24 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-01-10 12:24 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On 09.01.2020 19:30, Tomas Vondra wrote:
>
>>
>>>
>>>> 3 Still no one commented on GTT's transaction information
>>>> processing, they include
>>>> 3.1 Should gtt's frozenxid need to be care?
>>>> 3.2 gtt’s clog clean
>>>> 3.3 How to deal with "too old" gtt data
>>>>
>>>
>>> No idea what to do about this.
>>>
>>
>> I wonder what is the specific of GTT here?
>> The same problem takes place for normal (local) temp tables, doesn't it?
>>
>
> Not sure. TBH I'm not sure I understand what the issue actually is.
Just open session, create temporary table and insert some data in it.
Then in other session run 2^31 transactions (at my desktop it takes
about 2 hours).
As far as temp tables are not proceeded by vacuum, database is stalled:
ERROR: database is not accepting commands to avoid wraparound data
loss in database "postgres"
It seems to be quite dubious behavior and it is strange to me that
nobody complains about it.
We discuss many issues related with temp tables (statistic, parallel
queries,...) which seems to be less critical.
But this problem is not specific to GTT - it can be reproduced with
normal (local) temp tables.
This is why I wonder why do we need to solve it in GTT patch.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-11 14:00 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
1 sibling, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-01-11 14:00 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; +Cc: pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
> 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:
[application/octet-stream] global_temporary_table_v3-pg13.patch (151.1K, ../../C2F6C122-7951-400B-B708-FC4798A316D5@alibaba-inc.com/3-global_temporary_table_v3-pg13.patch)
download | inline diff:
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;
+
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-11 20:27 Pavel Stehule <pavel.stehule@gmail.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 2 replies; 525+ messages in thread
From: Pavel Stehule @ 2020-01-11 20:27 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
Hi
so 11. 1. 2020 v 15:00 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:
> Hi all
>
> This is the latest patch
>
> The updates are as follows:
> 1. Support global temp Inherit table global temp partition table
> 2. Support serial column in GTT
> 3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
> 4. Provide view pg_gtt_attached_pids to manage GTT
> 5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
> 6. Alter GTT or rename GTT is allowed under some conditions
>
>
> Please give me feedback.
>
I tested the functionality
1. i think so "ON COMMIT PRESERVE ROWS" should be default mode (like local
temp tables).
I tested some simple scripts
test01.sql
CREATE TEMP TABLE foo(a int, b int);
INSERT INTO foo SELECT random()*100, random()*1000 FROM
generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DROP TABLE foo; -- simulate disconnect
after 100 sec, the table pg_attribute has 3.2MB
and 64 tps, 6446 transaction
test02.sql
INSERT INTO foo SELECT random()*100, random()*1000 FROM
generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DELETE FROM foo; -- simulate disconnect
after 100 sec, 1688 tps, 168830 transactions
So performance is absolutely different as we expected.
From my perspective, this functionality is great.
Todo:
pg_table_size function doesn't work
Regards
Pavel
> Wenjing
>
>
>
>
>
> 2020年1月6日 上午4:06,Tomas Vondra <tomas.vondra@2ndquadrant.com> 写道:
>
> Hi,
>
> I think we need to do something with having two patches aiming to add
> global temporary tables:
>
> [1] https://commitfest.postgresql.org/26/2349/
>
> [2] https://commitfest.postgresql.org/26/2233/
>
> As a reviewer I have no idea which of the threads to look at - certainly
> not without reading both threads, which I doubt anyone will really do.
> The reviews and discussions are somewhat intermixed between those two
> threads, which makes it even more confusing.
>
> I think we should agree on a minimal patch combining the necessary/good
> bits from the various patches, and terminate one of the threads (i.e.
> mark it as rejected or RWF). And we need to do that now, otherwise
> there's about 0% chance of getting this into v13.
>
> In general, I agree with the sentiment Rober expressed in [1] - the
> patch needs to be as small as possible, not adding "nice to have"
> features (like support for parallel queries - I very much doubt just
> using shared instead of local buffers is enough to make it work.)
>
> regards
>
> --
> Tomas Vondra http://www.2ndQuadrant.com
> PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
>
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-12 01:14 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Tomas Vondra @ 2020-01-12 01:14 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On Fri, Jan 10, 2020 at 03:24:34PM +0300, Konstantin Knizhnik wrote:
>
>
>On 09.01.2020 19:30, Tomas Vondra wrote:
>
>
>>
>>>
>>>>
>>>>>3 Still no one commented on GTT's transaction information
>>>>>processing, they include
>>>>>3.1 Should gtt's frozenxid need to be care?
>>>>>3.2 gtt’s clog clean
>>>>>3.3 How to deal with "too old" gtt data
>>>>>
>>>>
>>>>No idea what to do about this.
>>>>
>>>
>>>I wonder what is the specific of GTT here?
>>>The same problem takes place for normal (local) temp tables, doesn't it?
>>>
>>
>>Not sure. TBH I'm not sure I understand what the issue actually is.
>
>Just open session, create temporary table and insert some data in it.
>Then in other session run 2^31 transactions (at my desktop it takes
>about 2 hours).
>As far as temp tables are not proceeded by vacuum, database is stalled:
>
> ERROR: database is not accepting commands to avoid wraparound data
>loss in database "postgres"
>
>It seems to be quite dubious behavior and it is strange to me that
>nobody complains about it.
>We discuss many issues related with temp tables (statistic, parallel
>queries,...) which seems to be less critical.
>
>But this problem is not specific to GTT - it can be reproduced with
>normal (local) temp tables.
>This is why I wonder why do we need to solve it in GTT patch.
>
Yeah, I think that's out of scope for GTT patch. Once we solve it for
plain temporary tables, we'll solve it for GTT too.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-12 01:51 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 2 replies; 525+ messages in thread
From: Tomas Vondra @ 2020-01-12 01:51 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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:
https://www.postgresql.org/message-id/CA%2BTgmoZFWaND4PpT_CJbeu6VZGZKi2rrTuSTL-Ykd97fexTN-w%40mail.g...
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-13 08:08 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
1 sibling, 2 replies; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-01-13 08:08 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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:
>
> https://www.postgresql.org/message-id/CA%2BTgmoZFWaND4PpT_CJbeu6VZGZKi2rrTuSTL-Ykd97fexTN-w%40mail.g...
>
>
"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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-13 16:32 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
1 sibling, 1 reply; 525+ messages in thread
From: Tomas Vondra @ 2020-01-13 16:32 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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:
>>
>>https://www.postgresql.org/message-id/CA%2BTgmoZFWaND4PpT_CJbeu6VZGZKi2rrTuSTL-Ykd97fexTN-w%40mail.g...
>>
>>
>"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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-13 20:12 Julien Rouhaud <rjuju123@gmail.com>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
0 siblings, 1 reply; 525+ messages in thread
From: Julien Rouhaud @ 2020-01-13 20:12 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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.
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-13 21:03 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Julien Rouhaud <rjuju123@gmail.com>
0 siblings, 0 replies; 525+ messages in thread
From: Tomas Vondra @ 2020-01-13 21:03 UTC (permalink / raw)
To: Julien Rouhaud <rjuju123@gmail.com>; +Cc: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-14 13:09 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
1 sibling, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-01-14 13:09 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
Thank you for review my patch.
> 2020年1月12日 上午4:27,Pavel Stehule <pavel.stehule@gmail.com> 写道:
>
> Hi
>
> so 11. 1. 2020 v 15:00 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
> Hi all
>
> This is the latest patch
>
> The updates are as follows:
> 1. Support global temp Inherit table global temp partition table
> 2. Support serial column in GTT
> 3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
> 4. Provide view pg_gtt_attached_pids to manage GTT
> 5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
> 6. Alter GTT or rename GTT is allowed under some conditions
>
>
> Please give me feedback.
>
> I tested the functionality
>
> 1. i think so "ON COMMIT PRESERVE ROWS" should be default mode (like local temp tables).
makes sense, I will fix it.
>
> I tested some simple scripts
>
> test01.sql
>
> CREATE TEMP TABLE foo(a int, b int);
> INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
> ANALYZE foo;
> SELECT sum(a), sum(b) FROM foo;
> DROP TABLE foo; -- simulate disconnect
>
>
> after 100 sec, the table pg_attribute has 3.2MB
> and 64 tps, 6446 transaction
>
> test02.sql
>
> INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
> ANALYZE foo;
> SELECT sum(a), sum(b) FROM foo;
> DELETE FROM foo; -- simulate disconnect
>
>
> after 100 sec, 1688 tps, 168830 transactions
>
> So performance is absolutely different as we expected.
>
> From my perspective, this functionality is great.
Yes, frequent ddl causes catalog bloat, GTT avoids this problem.
>
> Todo:
>
> pg_table_size function doesn't work
Do you mean that function pg_table_size() need get the storage space used by the one GTT in the entire db(include all session) .
>
> 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
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-14 13:20 Pavel Stehule <pavel.stehule@gmail.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 2 replies; 525+ messages in thread
From: Pavel Stehule @ 2020-01-14 13:20 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
út 14. 1. 2020 v 14:09 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:
> Thank you for review my patch.
>
>
> 2020年1月12日 上午4:27,Pavel Stehule <pavel.stehule@gmail.com> 写道:
>
> Hi
>
> so 11. 1. 2020 v 15:00 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
> napsal:
>
>> Hi all
>>
>> This is the latest patch
>>
>> The updates are as follows:
>> 1. Support global temp Inherit table global temp partition table
>> 2. Support serial column in GTT
>> 3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
>> 4. Provide view pg_gtt_attached_pids to manage GTT
>> 5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
>> 6. Alter GTT or rename GTT is allowed under some conditions
>>
>>
>> Please give me feedback.
>>
>
> I tested the functionality
>
> 1. i think so "ON COMMIT PRESERVE ROWS" should be default mode (like local
> temp tables).
>
> makes sense, I will fix it.
>
>
> I tested some simple scripts
>
> test01.sql
>
> CREATE TEMP TABLE foo(a int, b int);
> INSERT INTO foo SELECT random()*100, random()*1000 FROM
> generate_series(1,1000);
> ANALYZE foo;
> SELECT sum(a), sum(b) FROM foo;
> DROP TABLE foo; -- simulate disconnect
>
>
> after 100 sec, the table pg_attribute has 3.2MB
> and 64 tps, 6446 transaction
>
> test02.sql
>
> INSERT INTO foo SELECT random()*100, random()*1000 FROM
> generate_series(1,1000);
> ANALYZE foo;
> SELECT sum(a), sum(b) FROM foo;
> DELETE FROM foo; -- simulate disconnect
>
>
> after 100 sec, 1688 tps, 168830 transactions
>
> So performance is absolutely different as we expected.
>
> From my perspective, this functionality is great.
>
> Yes, frequent ddl causes catalog bloat, GTT avoids this problem.
>
>
> Todo:
>
> pg_table_size function doesn't work
>
> Do you mean that function pg_table_size() need get the storage space used
> by the one GTT in the entire db(include all session) .
>
It's question how much GTT tables should be similar to classic tables. But
the reporting in psql should to work \dt+, \l+, \di+
>
> 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
>>
>>
>>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-14 14:15 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
0 siblings, 0 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-01-14 14:15 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 2020年1月12日 上午9:14,Tomas Vondra <tomas.vondra@2ndquadrant.com> 写道:
>
> On Fri, Jan 10, 2020 at 03:24:34PM +0300, Konstantin Knizhnik wrote:
>>
>>
>> On 09.01.2020 19:30, Tomas Vondra wrote:
>>
>>
>>>
>>>>
>>>>>
>>>>>> 3 Still no one commented on GTT's transaction information processing, they include
>>>>>> 3.1 Should gtt's frozenxid need to be care?
>>>>>> 3.2 gtt’s clog clean
>>>>>> 3.3 How to deal with "too old" gtt data
>>>>>>
>>>>>
>>>>> No idea what to do about this.
>>>>>
>>>>
>>>> I wonder what is the specific of GTT here?
>>>> The same problem takes place for normal (local) temp tables, doesn't it?
>>>>
>>>
>>> Not sure. TBH I'm not sure I understand what the issue actually is.
>>
>> Just open session, create temporary table and insert some data in it.
>> Then in other session run 2^31 transactions (at my desktop it takes about 2 hours).
>> As far as temp tables are not proceeded by vacuum, database is stalled:
>>
>> ERROR: database is not accepting commands to avoid wraparound data loss in database "postgres"
>>
>> It seems to be quite dubious behavior and it is strange to me that nobody complains about it.
>> We discuss many issues related with temp tables (statistic, parallel queries,...) which seems to be less critical.
>>
>> But this problem is not specific to GTT - it can be reproduced with normal (local) temp tables.
>> This is why I wonder why do we need to solve it in GTT patch.
>>
>
> Yeah, I think that's out of scope for GTT patch. Once we solve it for
> plain temporary tables, we'll solve it for GTT too.
1. The core problem is that the data contains transaction information (xid), which needs to be vacuum(freeze) regularly to avoid running out of xid.
The autovacuum supports vacuum regular table but local temp does not. autovacuum also does not support GTT.
2. However, the difference between the local temp table and the global temp table(GTT) is that
a) For local temp table: one table hava one piece of data. the frozenxid of one local temp table is store in the catalog(pg_class).
b) For global temp table: each session has a separate copy of data, one GTT may contain maxbackend frozenxid.
and I don't think it's a good idea to keep frozenxid of GTT in the catalog(pg_class).
It becomes a question: how to handle GTT transaction information?
I agree that problem 1 should be completely solved by a some feature, such as local transactions. It is definitely not included in the GTT patch.
But, I think we need to ensure the durability of GTT data. For example, data in GTT cannot be lost due to the clog being cleaned up. It belongs to problem 2.
Wenjing
>
> regards
>
> --
> Tomas Vondra http://www.2ndQuadrant.com
> PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-14 14:16 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
1 sibling, 0 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-01-14 14:16 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 2020年1月14日 下午9:20,Pavel Stehule <pavel.stehule@gmail.com> 写道:
>
>
>
> út 14. 1. 2020 v 14:09 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
> Thank you for review my patch.
>
>
>> 2020年1月12日 上午4:27,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:
>>
>> Hi
>>
>> so 11. 1. 2020 v 15:00 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
>> Hi all
>>
>> This is the latest patch
>>
>> The updates are as follows:
>> 1. Support global temp Inherit table global temp partition table
>> 2. Support serial column in GTT
>> 3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
>> 4. Provide view pg_gtt_attached_pids to manage GTT
>> 5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
>> 6. Alter GTT or rename GTT is allowed under some conditions
>>
>>
>> Please give me feedback.
>>
>> I tested the functionality
>>
>> 1. i think so "ON COMMIT PRESERVE ROWS" should be default mode (like local temp tables).
> makes sense, I will fix it.
>
>>
>> I tested some simple scripts
>>
>> test01.sql
>>
>> CREATE TEMP TABLE foo(a int, b int);
>> INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
>> ANALYZE foo;
>> SELECT sum(a), sum(b) FROM foo;
>> DROP TABLE foo; -- simulate disconnect
>>
>>
>> after 100 sec, the table pg_attribute has 3.2MB
>> and 64 tps, 6446 transaction
>>
>> test02.sql
>>
>> INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
>> ANALYZE foo;
>> SELECT sum(a), sum(b) FROM foo;
>> DELETE FROM foo; -- simulate disconnect
>>
>>
>> after 100 sec, 1688 tps, 168830 transactions
>>
>> So performance is absolutely different as we expected.
>>
>> From my perspective, this functionality is great.
> Yes, frequent ddl causes catalog bloat, GTT avoids this problem.
>
>>
>> Todo:
>>
>> pg_table_size function doesn't work
> Do you mean that function pg_table_size() need get the storage space used by the one GTT in the entire db(include all session) .
>
> It's question how much GTT tables should be similar to classic tables. But the reporting in psql should to work \dt+, \l+, \di+
Get it, I will fix it.
>
>
>
>>
>> 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
>>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-15 13:10 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
1 sibling, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-01-15 13:10 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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.
>
>>
>>>>
>>>> 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:
>>
>> https://www.postgresql.org/message-id/CA%2BTgmoZFWaND4PpT_CJbeu6VZGZKi2rrTuSTL-Ykd97fexTN-w%40mail.g...
>>
> "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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-16 07:23 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 0 replies; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-01-16 07:23 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-19 17:04 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
1 sibling, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-01-19 17:04 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; +Cc: Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 2020年1月14日 下午9:20,Pavel Stehule <pavel.stehule@gmail.com> 写道:
>
>
>
> út 14. 1. 2020 v 14:09 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
> Thank you for review my patch.
>
>
>> 2020年1月12日 上午4:27,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:
>>
>> Hi
>>
>> so 11. 1. 2020 v 15:00 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
>> Hi all
>>
>> This is the latest patch
>>
>> The updates are as follows:
>> 1. Support global temp Inherit table global temp partition table
>> 2. Support serial column in GTT
>> 3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
>> 4. Provide view pg_gtt_attached_pids to manage GTT
>> 5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
>> 6. Alter GTT or rename GTT is allowed under some conditions
>>
>>
>> Please give me feedback.
>>
>> I tested the functionality
>>
>> 1. i think so "ON COMMIT PRESERVE ROWS" should be default mode (like local temp tables).
> makes sense, I will fix it.
>
>>
>> I tested some simple scripts
>>
>> test01.sql
>>
>> CREATE TEMP TABLE foo(a int, b int);
>> INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
>> ANALYZE foo;
>> SELECT sum(a), sum(b) FROM foo;
>> DROP TABLE foo; -- simulate disconnect
>>
>>
>> after 100 sec, the table pg_attribute has 3.2MB
>> and 64 tps, 6446 transaction
>>
>> test02.sql
>>
>> INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
>> ANALYZE foo;
>> SELECT sum(a), sum(b) FROM foo;
>> DELETE FROM foo; -- simulate disconnect
>>
>>
>> after 100 sec, 1688 tps, 168830 transactions
>>
>> So performance is absolutely different as we expected.
>>
>> From my perspective, this functionality is great.
> Yes, frequent ddl causes catalog bloat, GTT avoids this problem.
>
>>
>> Todo:
>>
>> pg_table_size function doesn't work
> Do you mean that function pg_table_size() need get the storage space used by the one GTT in the entire db(include all session) .
>
> It's question how much GTT tables should be similar to classic tables. But the reporting in psql should to work \dt+, \l+, \di+
I have fixed this problem.
Please let me know where I need to improve.
Thanks
Wenjing
>
>
>
>>
>> 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:
[application/octet-stream] global_temporary_table_v4-pg13.patch (159.2K, ../../3A07073C-AF60-4C5E-859F-81A82911B671@alibaba-inc.com/3-global_temporary_table_v4-pg13.patch)
download | inline diff:
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;
+
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-19 17:32 Erik Rijkers <er@xs4all.nl>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 1 reply; 525+ messages in thread
From: Erik Rijkers @ 2020-01-19 17:32 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-20 16:27 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Erik Rijkers <er@xs4all.nl>
0 siblings, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-01-20 16:27 UTC (permalink / raw)
To: Erik Rijkers <er@xs4all.nl>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 2020年1月20日 上午1:32,Erik Rijkers <er@xs4all.nl> 写道:
>
> On 2020-01-19 18:04, 曾文旌(义从) wrote:
>>> 2020年1月14日 下午9:20,Pavel Stehule <pavel.stehule@gmail.com> 写道:
>>> út 14. 1. 2020 v 14:09 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
>
>>> [global_temporary_table_v4-pg13.patch ]
>
> Hi,
>
> This patch doesn't quiet apply for me:
>
> patching file src/backend/access/common/reloptions.c
> patching file src/backend/access/gist/gistutil.c
> patching file src/backend/access/hash/hash.c
> Hunk #1 succeeded at 149 (offset 3 lines).
> patching file src/backend/access/heap/heapam_handler.c
> patching file src/backend/access/heap/vacuumlazy.c
> patching file src/backend/access/nbtree/nbtpage.c
> patching file src/backend/access/table/tableam.c
> patching file src/backend/access/transam/xlog.c
> patching file src/backend/catalog/Makefile
> Hunk #1 FAILED at 44.
> 1 out of 1 hunk FAILED -- saving rejects to file src/backend/catalog/Makefile.rej
> [...]
> (The rest applies without errors)
>
> src/backend/catalog/Makefile.rej contains:
>
> ------------------------
> --- src/backend/catalog/Makefile
> +++ src/backend/catalog/Makefile
> @@ -44,6 +44,8 @@ OBJS = \
> storage.o \
> toasting.o
>
> +OBJS += storage_gtt.o
> +
> BKIFILES = postgres.bki postgres.description postgres.shdescription
>
> include $(top_srcdir)/src/backend/common.mk
> ------------------------
>
> Can you have a look?
I updated the code and remade the patch.
Please give me feedback if you have any more questions.
>
>
> thanks,
>
> Erik Rijkers
>
>
>
>
>
Attachments:
[application/octet-stream] global_temporary_table_v5-pg13.patch (159.2K, ../../9DB9B56D-EF23-45CB-8AF4-0129D736446A@alibaba-inc.com/2-global_temporary_table_v5-pg13.patch)
download | inline diff:
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;
+
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-21 05:43 Pavel Stehule <pavel.stehule@gmail.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2020-01-21 05:43 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Erik Rijkers <er@xs4all.nl>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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?
Regards
Pavel
po 20. 1. 2020 v 17:27 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:
>
>
> > 2020年1月20日 上午1:32,Erik Rijkers <er@xs4all.nl> 写道:
> >
> > On 2020-01-19 18:04, 曾文旌(义从) wrote:
> >>> 2020年1月14日 下午9:20,Pavel Stehule <pavel.stehule@gmail.com> 写道:
> >>> út 14. 1. 2020 v 14:09 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com
> <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
> >
> >>> [global_temporary_table_v4-pg13.patch ]
> >
> > Hi,
> >
> > This patch doesn't quiet apply for me:
> >
> > patching file src/backend/access/common/reloptions.c
> > patching file src/backend/access/gist/gistutil.c
> > patching file src/backend/access/hash/hash.c
> > Hunk #1 succeeded at 149 (offset 3 lines).
> > patching file src/backend/access/heap/heapam_handler.c
> > patching file src/backend/access/heap/vacuumlazy.c
> > patching file src/backend/access/nbtree/nbtpage.c
> > patching file src/backend/access/table/tableam.c
> > patching file src/backend/access/transam/xlog.c
> > patching file src/backend/catalog/Makefile
> > Hunk #1 FAILED at 44.
> > 1 out of 1 hunk FAILED -- saving rejects to file
> src/backend/catalog/Makefile.rej
> > [...]
> > (The rest applies without errors)
> >
> > src/backend/catalog/Makefile.rej contains:
> >
> > ------------------------
> > --- src/backend/catalog/Makefile
> > +++ src/backend/catalog/Makefile
> > @@ -44,6 +44,8 @@ OBJS = \
> > storage.o \
> > toasting.o
> >
> > +OBJS += storage_gtt.o
> > +
> > BKIFILES = postgres.bki postgres.description postgres.shdescription
> >
> > include $(top_srcdir)/src/backend/common.mk
> > ------------------------
> >
> > Can you have a look?
> I updated the code and remade the patch.
> Please give me feedback if you have any more questions.
>
>
>
>
> >
> >
> > thanks,
> >
> > Erik Rijkers
> >
> >
> >
> >
> >
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-21 08:45 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
1 sibling, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-01-21 08:45 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 2020年1月12日 上午4:27,Pavel Stehule <pavel.stehule@gmail.com> 写道:
>
> Hi
>
> so 11. 1. 2020 v 15:00 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
> Hi all
>
> This is the latest patch
>
> The updates are as follows:
> 1. Support global temp Inherit table global temp partition table
> 2. Support serial column in GTT
> 3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
> 4. Provide view pg_gtt_attached_pids to manage GTT
> 5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
> 6. Alter GTT or rename GTT is allowed under some conditions
>
>
> Please give me feedback.
>
> I tested the functionality
>
> 1. i think so "ON COMMIT PRESERVE ROWS" should be default mode (like local temp tables).
ON COMMIT PRESERVE ROWS is default mode now.
Wenjing
>
> I tested some simple scripts
>
> test01.sql
>
> CREATE TEMP TABLE foo(a int, b int);
> INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
> ANALYZE foo;
> SELECT sum(a), sum(b) FROM foo;
> DROP TABLE foo; -- simulate disconnect
>
>
> after 100 sec, the table pg_attribute has 3.2MB
> and 64 tps, 6446 transaction
>
> test02.sql
>
> INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
> ANALYZE foo;
> SELECT sum(a), sum(b) FROM foo;
> DELETE FROM foo; -- simulate disconnect
>
>
> after 100 sec, 1688 tps, 168830 transactions
>
> So performance is absolutely different as we expected.
>
> From my perspective, this functionality is great.
>
> Todo:
>
> pg_table_size function doesn't work
>
> Regards
>
> Pavel
>
>
> Wenjing
>
>
>
>
>
>> 2020年1月6日 上午4:06,Tomas Vondra <tomas.vondra@2ndquadrant.com <mailto:tomas.vondra@2ndquadrant.com>> 写道:
>>
>> Hi,
>>
>> I think we need to do something with having two patches aiming to add
>> global temporary tables:
>>
>> [1] https://commitfest.postgresql.org/26/2349/ <https://commitfest.postgresql.org/26/2349/;
>>
>> [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:
[application/octet-stream] global_temporary_table_v6-pg13.patch (159.3K, ../../E161CC21-F228-4189-9FD8-3E96DCC38279@alibaba-inc.com/3-global_temporary_table_v6-pg13.patch)
download | inline diff:
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;
+
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-21 18:51 Pavel Stehule <pavel.stehule@gmail.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2020-01-21 18:51 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
út 21. 1. 2020 v 9:46 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:
>
>
> 2020年1月12日 上午4:27,Pavel Stehule <pavel.stehule@gmail.com> 写道:
>
> Hi
>
> so 11. 1. 2020 v 15:00 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
> napsal:
>
>> Hi all
>>
>> This is the latest patch
>>
>> The updates are as follows:
>> 1. Support global temp Inherit table global temp partition table
>> 2. Support serial column in GTT
>> 3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
>> 4. Provide view pg_gtt_attached_pids to manage GTT
>> 5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
>> 6. Alter GTT or rename GTT is allowed under some conditions
>>
>>
>> Please give me feedback.
>>
>
> I tested the functionality
>
> 1. i think so "ON COMMIT PRESERVE ROWS" should be default mode (like local
> temp tables).
>
>
> ON COMMIT PRESERVE ROWS is default mode now.
>
Thank you
* I tried to create global temp table with index. When I tried to drop this
table (and this table was used by second instance), then I got error message
postgres=# drop table foo;
ERROR: can not drop index when other backend attached this global temp
table
It is expected, but it is not too much user friendly. Is better to check if
you can drop table, then lock it, and then drop all objects.
* 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
>
> Wenjing
>
>
>
>
> I tested some simple scripts
>
> test01.sql
>
> CREATE TEMP TABLE foo(a int, b int);
> INSERT INTO foo SELECT random()*100, random()*1000 FROM
> generate_series(1,1000);
> ANALYZE foo;
> SELECT sum(a), sum(b) FROM foo;
> DROP TABLE foo; -- simulate disconnect
>
>
> after 100 sec, the table pg_attribute has 3.2MB
> and 64 tps, 6446 transaction
>
> test02.sql
>
> INSERT INTO foo SELECT random()*100, random()*1000 FROM
> generate_series(1,1000);
> ANALYZE foo;
> SELECT sum(a), sum(b) FROM foo;
> DELETE FROM foo; -- simulate disconnect
>
>
> after 100 sec, 1688 tps, 168830 transactions
>
> So performance is absolutely different as we expected.
>
> From my perspective, this functionality is great.
>
> Todo:
>
> pg_table_size function doesn't work
>
> Regards
>
> Pavel
>
>
>> Wenjing
>>
>>
>>
>>
>>
>> 2020年1月6日 上午4:06,Tomas Vondra <tomas.vondra@2ndquadrant.com> 写道:
>>
>> Hi,
>>
>> I think we need to do something with having two patches aiming to add
>> global temporary tables:
>>
>> [1] https://commitfest.postgresql.org/26/2349/
>>
>> [2] https://commitfest.postgresql.org/26/2233/
>>
>> As a reviewer I have no idea which of the threads to look at - certainly
>> not without reading both threads, which I doubt anyone will really do.
>> The reviews and discussions are somewhat intermixed between those two
>> threads, which makes it even more confusing.
>>
>> I think we should agree on a minimal patch combining the necessary/good
>> bits from the various patches, and terminate one of the threads (i.e.
>> mark it as rejected or RWF). And we need to do that now, otherwise
>> there's about 0% chance of getting this into v13.
>>
>> In general, I agree with the sentiment Rober expressed in [1] - the
>> patch needs to be as small as possible, not adding "nice to have"
>> features (like support for parallel queries - I very much doubt just
>> using shared instead of local buffers is enough to make it work.)
>>
>> regards
>>
>> --
>> Tomas Vondra http://www.2ndQuadrant.com
>> <http://www.2ndquadrant.com/;
>> PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
>>
>>
>>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-22 05:29 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-01-22 05:29 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Erik Rijkers <er@xs4all.nl>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@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.
>
> Regards
>
> Pavel
>
> po 20. 1. 2020 v 17:27 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
>
>
> > 2020年1月20日 上午1:32,Erik Rijkers <er@xs4all.nl <mailto:er@xs4all.nl>> 写道:
> >
> > On 2020-01-19 18:04, 曾文旌(义从) wrote:
> >>> 2020年1月14日 下午9:20,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:
> >>> út 14. 1. 2020 v 14:09 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com> <mailto:wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>>> napsal:
> >
> >>> [global_temporary_table_v4-pg13.patch ]
> >
> > Hi,
> >
> > This patch doesn't quiet apply for me:
> >
> > patching file src/backend/access/common/reloptions.c
> > patching file src/backend/access/gist/gistutil.c
> > patching file src/backend/access/hash/hash.c
> > Hunk #1 succeeded at 149 (offset 3 lines).
> > patching file src/backend/access/heap/heapam_handler.c
> > patching file src/backend/access/heap/vacuumlazy.c
> > patching file src/backend/access/nbtree/nbtpage.c
> > patching file src/backend/access/table/tableam.c
> > patching file src/backend/access/transam/xlog.c
> > patching file src/backend/catalog/Makefile
> > Hunk #1 FAILED at 44.
> > 1 out of 1 hunk FAILED -- saving rejects to file src/backend/catalog/Makefile.rej
> > [...]
> > (The rest applies without errors)
> >
> > src/backend/catalog/Makefile.rej contains:
> >
> > ------------------------
> > --- src/backend/catalog/Makefile
> > +++ src/backend/catalog/Makefile
> > @@ -44,6 +44,8 @@ OBJS = \
> > storage.o \
> > toasting.o
> >
> > +OBJS += storage_gtt.o
> > +
> > BKIFILES = postgres.bki postgres.description postgres.shdescription
> >
> > include $(top_srcdir)/src/backend/common.mk <http://common.mk/;
> > ------------------------
> >
> > 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
> >
> >
> >
> >
> >
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-22 06:16 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-01-22 06:16 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 2020年1月22日 上午2:51,Pavel Stehule <pavel.stehule@gmail.com> 写道:
>
>
>
> út 21. 1. 2020 v 9:46 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
>
>
>> 2020年1月12日 上午4:27,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:
>>
>> Hi
>>
>> so 11. 1. 2020 v 15:00 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
>> Hi all
>>
>> This is the latest patch
>>
>> The updates are as follows:
>> 1. Support global temp Inherit table global temp partition table
>> 2. Support serial column in GTT
>> 3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
>> 4. Provide view pg_gtt_attached_pids to manage GTT
>> 5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
>> 6. Alter GTT or rename GTT is allowed under some conditions
>>
>>
>> Please give me feedback.
>>
>> I tested the functionality
>>
>> 1. i think so "ON COMMIT PRESERVE ROWS" should be default mode (like local temp tables).
>
> ON COMMIT PRESERVE ROWS is default mode now.
>
> Thank you
>
> * I tried to create global temp table with index. When I tried to drop this table (and this table was used by second instance), then I got error message
>
> postgres=# drop table foo;
> ERROR: can not drop index when other backend attached this global temp table
>
> It is expected, but it is not too much user friendly. Is better to check if you can drop table, then lock it, and then drop all objects.
I don't understand what needs to be improved. Could you describe it in detail?
>
> * tab complete can be nice for CREATE GLOBAL TEMP table
Yes, I will improve it.
>
> \dt+ \di+ doesn't work correctly, or maybe I don't understand to the implementation.
>
postgres=# create table t(a int primary key);
CREATE TABLE
postgres=# create global temp table gt(a int primary key);
CREATE TABLE
postgres=# insert into t values(generate_series(1,10000));
INSERT 0 10000
postgres=# insert into gt values(generate_series(1,10000));
INSERT 0 10000
postgres=# \dt+
List of relations
Schema | Name | Type | Owner | Persistence | Size | Description
--------+------+-------+-------------+-------------+--------+-------------
public | gt | table | wenjing.zwj | session | 384 kB |
public | t | table | wenjing.zwj | permanent | 384 kB |
(2 rows)
postgres=# \di+
List of relations
Schema | Name | Type | Owner | Table | Persistence | Size | Description
--------+---------+-------+-------------+-------+-------------+--------+-------------
public | gt_pkey | index | wenjing.zwj | gt | session | 240 kB |
public | t_pkey | index | wenjing.zwj | t | permanent | 240 kB |
(2 rows)
> I see same size in all sessions. Global temp tables shares same files?
No, they use their own files.
But \dt+ \di+ counts the total file sizes in all sessions for each GTT.
Wenjing
>
> Regards
>
> Pavel
>
>
>
>
>
> Wenjing
>
>
>
>>
>> I tested some simple scripts
>>
>> test01.sql
>>
>> CREATE TEMP TABLE foo(a int, b int);
>> INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
>> ANALYZE foo;
>> SELECT sum(a), sum(b) FROM foo;
>> DROP TABLE foo; -- simulate disconnect
>>
>>
>> after 100 sec, the table pg_attribute has 3.2MB
>> and 64 tps, 6446 transaction
>>
>> test02.sql
>>
>> INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
>> ANALYZE foo;
>> SELECT sum(a), sum(b) FROM foo;
>> DELETE FROM foo; -- simulate disconnect
>>
>>
>> after 100 sec, 1688 tps, 168830 transactions
>>
>> So performance is absolutely different as we expected.
>>
>> From my perspective, this functionality is great.
>>
>> Todo:
>>
>> pg_table_size function doesn't work
>>
>> Regards
>>
>> Pavel
>>
>>
>> Wenjing
>>
>>
>>
>>
>>
>>> 2020年1月6日 上午4:06,Tomas Vondra <tomas.vondra@2ndquadrant.com <mailto:tomas.vondra@2ndquadrant.com>> 写道:
>>>
>>> Hi,
>>>
>>> I think we need to do something with having two patches aiming to add
>>> global temporary tables:
>>>
>>> [1] https://commitfest.postgresql.org/26/2349/ <https://commitfest.postgresql.org/26/2349/;
>>>
>>> [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
>>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-22 06:31 Pavel Stehule <pavel.stehule@gmail.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2020-01-22 06:31 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
st 22. 1. 2020 v 7:16 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:
>
>
> 2020年1月22日 上午2:51,Pavel Stehule <pavel.stehule@gmail.com> 写道:
>
>
>
> út 21. 1. 2020 v 9:46 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
> napsal:
>
>>
>>
>> 2020年1月12日 上午4:27,Pavel Stehule <pavel.stehule@gmail.com> 写道:
>>
>> Hi
>>
>> so 11. 1. 2020 v 15:00 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
>> napsal:
>>
>>> Hi all
>>>
>>> This is the latest patch
>>>
>>> The updates are as follows:
>>> 1. Support global temp Inherit table global temp partition table
>>> 2. Support serial column in GTT
>>> 3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
>>> 4. Provide view pg_gtt_attached_pids to manage GTT
>>> 5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
>>> 6. Alter GTT or rename GTT is allowed under some conditions
>>>
>>>
>>> Please give me feedback.
>>>
>>
>> I tested the functionality
>>
>> 1. i think so "ON COMMIT PRESERVE ROWS" should be default mode (like
>> local temp tables).
>>
>>
>> ON COMMIT PRESERVE ROWS is default mode now.
>>
>
> Thank you
>
> * I tried to create global temp table with index. When I tried to drop
> this table (and this table was used by second instance), then I got error
> message
>
> postgres=# drop table foo;
> ERROR: can not drop index when other backend attached this global temp
> table
>
> It is expected, but it is not too much user friendly. Is better to check
> if you can drop table, then lock it, and then drop all objects.
>
> I don't understand what needs to be improved. Could you describe it in
> detail?
>
the error messages should be some like
can not drop table when other backend attached this global temp table.
It is little bit messy, when you try to drop table and you got message
about index
>
> * tab complete can be nice for CREATE GLOBAL TEMP table
>
> Yes, I will improve it.
>
>
> \dt+ \di+ doesn't work correctly, or maybe I don't understand to the
> implementation.
>
>
> postgres=# create table t(a int primary key);
> CREATE TABLE
> postgres=# create global temp table gt(a int primary key);
> CREATE TABLE
> postgres=# insert into t values(generate_series(1,10000));
> INSERT 0 10000
> postgres=# insert into gt values(generate_series(1,10000));
> INSERT 0 10000
>
> postgres=# \dt+
> List of relations
> Schema | Name | Type | Owner | Persistence | Size | Description
> --------+------+-------+-------------+-------------+--------+-------------
> public | gt | table | wenjing.zwj | session | 384 kB |
> public | t | table | wenjing.zwj | permanent | 384 kB |
> (2 rows)
>
> postgres=# \di+
> List of relations
> Schema | Name | Type | Owner | Table | Persistence | Size |
> Description
>
> --------+---------+-------+-------------+-------+-------------+--------+-------------
> public | gt_pkey | index | wenjing.zwj | gt | session | 240 kB |
> public | t_pkey | index | wenjing.zwj | t | permanent | 240 kB |
> (2 rows)
>
>
> I see same size in all sessions. Global temp tables shares same files?
>
> No, they use their own files.
> But \dt+ \di+ counts the total file sizes in all sessions for each GTT.
>
I think so it is wrong. The data are independent and the sizes should be
independent too
>
>
> Wenjing
>
>
> Regards
>
> Pavel
>
>
>
>
>>
>> Wenjing
>>
>>
>>
>>
>> I tested some simple scripts
>>
>> test01.sql
>>
>> CREATE TEMP TABLE foo(a int, b int);
>> INSERT INTO foo SELECT random()*100, random()*1000 FROM
>> generate_series(1,1000);
>> ANALYZE foo;
>> SELECT sum(a), sum(b) FROM foo;
>> DROP TABLE foo; -- simulate disconnect
>>
>>
>> after 100 sec, the table pg_attribute has 3.2MB
>> and 64 tps, 6446 transaction
>>
>> test02.sql
>>
>> INSERT INTO foo SELECT random()*100, random()*1000 FROM
>> generate_series(1,1000);
>> ANALYZE foo;
>> SELECT sum(a), sum(b) FROM foo;
>> DELETE FROM foo; -- simulate disconnect
>>
>>
>> after 100 sec, 1688 tps, 168830 transactions
>>
>> So performance is absolutely different as we expected.
>>
>> From my perspective, this functionality is great.
>>
>> Todo:
>>
>> pg_table_size function doesn't work
>>
>> Regards
>>
>> Pavel
>>
>>
>>> Wenjing
>>>
>>>
>>>
>>>
>>>
>>> 2020年1月6日 上午4:06,Tomas Vondra <tomas.vondra@2ndquadrant.com> 写道:
>>>
>>> Hi,
>>>
>>> I think we need to do something with having two patches aiming to add
>>> global temporary tables:
>>>
>>> [1] https://commitfest.postgresql.org/26/2349/
>>>
>>> [2] https://commitfest.postgresql.org/26/2233/
>>>
>>> As a reviewer I have no idea which of the threads to look at - certainly
>>> not without reading both threads, which I doubt anyone will really do.
>>> The reviews and discussions are somewhat intermixed between those two
>>> threads, which makes it even more confusing.
>>>
>>> I think we should agree on a minimal patch combining the necessary/good
>>> bits from the various patches, and terminate one of the threads (i.e.
>>> mark it as rejected or RWF). And we need to do that now, otherwise
>>> there's about 0% chance of getting this into v13.
>>>
>>> In general, I agree with the sentiment Rober expressed in [1] - the
>>> patch needs to be as small as possible, not adding "nice to have"
>>> features (like support for parallel queries - I very much doubt just
>>> using shared instead of local buffers is enough to make it work.)
>>>
>>> regards
>>>
>>> --
>>> Tomas Vondra http://www.2ndQuadrant.com
>>> <http://www.2ndquadrant.com/;
>>> PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
>>>
>>>
>>>
>>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-23 16:22 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 0 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-01-23 16:22 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 2020年1月22日 下午2:31,Pavel Stehule <pavel.stehule@gmail.com> 写道:
>
>
>
> st 22. 1. 2020 v 7:16 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
>
>
>> 2020年1月22日 上午2:51,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:
>>
>>
>>
>> út 21. 1. 2020 v 9:46 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
>>
>>
>>> 2020年1月12日 上午4:27,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:
>>>
>>> Hi
>>>
>>> so 11. 1. 2020 v 15:00 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
>>> Hi all
>>>
>>> This is the latest patch
>>>
>>> The updates are as follows:
>>> 1. Support global temp Inherit table global temp partition table
>>> 2. Support serial column in GTT
>>> 3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
>>> 4. Provide view pg_gtt_attached_pids to manage GTT
>>> 5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
>>> 6. Alter GTT or rename GTT is allowed under some conditions
>>>
>>>
>>> Please give me feedback.
>>>
>>> I tested the functionality
>>>
>>> 1. i think so "ON COMMIT PRESERVE ROWS" should be default mode (like local temp tables).
>>
>> ON COMMIT PRESERVE ROWS is default mode now.
>>
>> Thank you
>>
>> * I tried to create global temp table with index. When I tried to drop this table (and this table was used by second instance), then I got error message
>>
>> postgres=# drop table foo;
>> ERROR: can not drop index when other backend attached this global temp table
>>
>> It is expected, but it is not too much user friendly. Is better to check if you can drop table, then lock it, and then drop all objects.
> I don't understand what needs to be improved. Could you describe it in detail?
>
> the error messages should be some like
>
> can not drop table when other backend attached this global temp table.
>
> It is little bit messy, when you try to drop table and you got message about index
It has been repaired in global_temporary_table_v7-pg13.patch
>
>
>>
>> * tab complete can be nice for CREATE GLOBAL TEMP table
> Yes, I will improve it.
It has been repaired in global_temporary_table_v7-pg13.patch
>>
>> \dt+ \di+ doesn't work correctly, or maybe I don't understand to the implementation.
>>
>
> postgres=# create table t(a int primary key);
> CREATE TABLE
> postgres=# create global temp table gt(a int primary key);
> CREATE TABLE
> postgres=# insert into t values(generate_series(1,10000));
> INSERT 0 10000
> postgres=# insert into gt values(generate_series(1,10000));
> INSERT 0 10000
>
> postgres=# \dt+
> List of relations
> Schema | Name | Type | Owner | Persistence | Size | Description
> --------+------+-------+-------------+-------------+--------+-------------
> public | gt | table | wenjing.zwj | session | 384 kB |
> public | t | table | wenjing.zwj | permanent | 384 kB |
> (2 rows)
>
> postgres=# \di+
> List of relations
> Schema | Name | Type | Owner | Table | Persistence | Size | Description
> --------+---------+-------+-------------+-------+-------------+--------+-------------
> public | gt_pkey | index | wenjing.zwj | gt | session | 240 kB |
> public | t_pkey | index | wenjing.zwj | t | permanent | 240 kB |
> (2 rows)
>
>
>> I see same size in all sessions. Global temp tables shares same files?
> No, they use their own files.
> But \dt+ \di+ counts the total file sizes in all sessions for each GTT.
>
> I think so it is wrong. The data are independent and the sizes should be independent too
It has been repaired in global_temporary_table_v7-pg13.patch.
Wenjing
>
>
>
>
> Wenjing
>
>>
>> Regards
>>
>> Pavel
>>
>>
>>
>>
>>
>> Wenjing
>>
>>
>>
>>>
>>> I tested some simple scripts
>>>
>>> test01.sql
>>>
>>> CREATE TEMP TABLE foo(a int, b int);
>>> INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
>>> ANALYZE foo;
>>> SELECT sum(a), sum(b) FROM foo;
>>> DROP TABLE foo; -- simulate disconnect
>>>
>>>
>>> after 100 sec, the table pg_attribute has 3.2MB
>>> and 64 tps, 6446 transaction
>>>
>>> test02.sql
>>>
>>> INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
>>> ANALYZE foo;
>>> SELECT sum(a), sum(b) FROM foo;
>>> DELETE FROM foo; -- simulate disconnect
>>>
>>>
>>> after 100 sec, 1688 tps, 168830 transactions
>>>
>>> So performance is absolutely different as we expected.
>>>
>>> From my perspective, this functionality is great.
>>>
>>> Todo:
>>>
>>> pg_table_size function doesn't work
>>>
>>> Regards
>>>
>>> Pavel
>>>
>>>
>>> Wenjing
>>>
>>>
>>>
>>>
>>>
>>>> 2020年1月6日 上午4:06,Tomas Vondra <tomas.vondra@2ndquadrant.com <mailto:tomas.vondra@2ndquadrant.com>> 写道:
>>>>
>>>> Hi,
>>>>
>>>> I think we need to do something with having two patches aiming to add
>>>> global temporary tables:
>>>>
>>>> [1] https://commitfest.postgresql.org/26/2349/ <https://commitfest.postgresql.org/26/2349/;
>>>>
>>>> [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:
[application/octet-stream] global_temporary_table_v7-pg13.patch (146.2K, ../../8B1EEE23-D7A0-419B-9A41-C6FBDB49C441@alibaba-inc.com/3-global_temporary_table_v7-pg13.patch)
download | inline diff:
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;
+
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-23 16:28 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 2 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-01-23 16:28 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Erik Rijkers <er@xs4all.nl>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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
>
>>
>> Regards
>>
>> Pavel
>>
>> po 20. 1. 2020 v 17:27 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
>>
>>
>> > 2020年1月20日 上午1:32,Erik Rijkers <er@xs4all.nl <mailto:er@xs4all.nl>> 写道:
>> >
>> > On 2020-01-19 18:04, 曾文旌(义从) wrote:
>> >>> 2020年1月14日 下午9:20,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:
>> >>> út 14. 1. 2020 v 14:09 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com> <mailto:wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>>> napsal:
>> >
>> >>> [global_temporary_table_v4-pg13.patch ]
>> >
>> > Hi,
>> >
>> > This patch doesn't quiet apply for me:
>> >
>> > patching file src/backend/access/common/reloptions.c
>> > patching file src/backend/access/gist/gistutil.c
>> > patching file src/backend/access/hash/hash.c
>> > Hunk #1 succeeded at 149 (offset 3 lines).
>> > patching file src/backend/access/heap/heapam_handler.c
>> > patching file src/backend/access/heap/vacuumlazy.c
>> > patching file src/backend/access/nbtree/nbtpage.c
>> > patching file src/backend/access/table/tableam.c
>> > patching file src/backend/access/transam/xlog.c
>> > patching file src/backend/catalog/Makefile
>> > Hunk #1 FAILED at 44.
>> > 1 out of 1 hunk FAILED -- saving rejects to file src/backend/catalog/Makefile.rej
>> > [...]
>> > (The rest applies without errors)
>> >
>> > src/backend/catalog/Makefile.rej contains:
>> >
>> > ------------------------
>> > --- src/backend/catalog/Makefile
>> > +++ src/backend/catalog/Makefile
>> > @@ -44,6 +44,8 @@ OBJS = \
>> > storage.o \
>> > toasting.o
>> >
>> > +OBJS += storage_gtt.o
>> > +
>> > BKIFILES = postgres.bki postgres.description postgres.shdescription
>> >
>> > include $(top_srcdir)/src/backend/common.mk <http://common.mk/;
>> > ------------------------
>> >
>> > 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:
[application/octet-stream] global_temporary_table_v7-pg13.patch (146.2K, ../../3D295871-7918-4019-A6F8-22C792C390E7@alibaba-inc.com/3-global_temporary_table_v7-pg13.patch)
download | inline diff:
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;
+
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-23 17:21 Pavel Stehule <pavel.stehule@gmail.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
1 sibling, 0 replies; 525+ messages in thread
From: Pavel Stehule @ 2020-01-23 17:21 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Erik Rijkers <er@xs4all.nl>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
č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
>
> Thanks
>
> Wenjing
>
>
>
>
>
> Regards
>
> Pavel
>
> po 20. 1. 2020 v 17:27 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
> napsal:
>
>>
>>
>> > 2020年1月20日 上午1:32,Erik Rijkers <er@xs4all.nl> 写道:
>> >
>> > On 2020-01-19 18:04, 曾文旌(义从) wrote:
>> >>> 2020年1月14日 下午9:20,Pavel Stehule <pavel.stehule@gmail.com> 写道:
>> >>> út 14. 1. 2020 v 14:09 odesílatel 曾文旌(义从) <
>> wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
>> >
>> >>> [global_temporary_table_v4-pg13.patch ]
>> >
>> > Hi,
>> >
>> > This patch doesn't quiet apply for me:
>> >
>> > patching file src/backend/access/common/reloptions.c
>> > patching file src/backend/access/gist/gistutil.c
>> > patching file src/backend/access/hash/hash.c
>> > Hunk #1 succeeded at 149 (offset 3 lines).
>> > patching file src/backend/access/heap/heapam_handler.c
>> > patching file src/backend/access/heap/vacuumlazy.c
>> > patching file src/backend/access/nbtree/nbtpage.c
>> > patching file src/backend/access/table/tableam.c
>> > patching file src/backend/access/transam/xlog.c
>> > patching file src/backend/catalog/Makefile
>> > Hunk #1 FAILED at 44.
>> > 1 out of 1 hunk FAILED -- saving rejects to file
>> src/backend/catalog/Makefile.rej
>> > [...]
>> > (The rest applies without errors)
>> >
>> > src/backend/catalog/Makefile.rej contains:
>> >
>> > ------------------------
>> > --- src/backend/catalog/Makefile
>> > +++ src/backend/catalog/Makefile
>> > @@ -44,6 +44,8 @@ OBJS = \
>> > storage.o \
>> > toasting.o
>> >
>> > +OBJS += storage_gtt.o
>> > +
>> > BKIFILES = postgres.bki postgres.description postgres.shdescription
>> >
>> > include $(top_srcdir)/src/backend/common.mk
>> > ------------------------
>> >
>> > Can you have a look?
>> I updated the code and remade the patch.
>> Please give me feedback if you have any more questions.
>>
>>
>>
>>
>> >
>> >
>> > thanks,
>> >
>> > Erik Rijkers
>> >
>> >
>> >
>> >
>> >
>>
>>
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-23 20:47 Robert Haas <robertmhaas@gmail.com>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
1 sibling, 2 replies; 525+ messages in thread
From: Robert Haas @ 2020-01-23 20:47 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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.
> 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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-24 08:20 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
1 sibling, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-01-24 08:20 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Erik Rijkers <er@xs4all.nl>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-24 08:39 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-01-24 08:39 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-24 09:09 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2020-01-24 09:09 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
>
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-24 09:43 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-01-24 09:43 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-24 12:15 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2020-01-24 12:15 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
pá 24. 1. 2020 v 10:43 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:
>
>
> On 24.01.2020 12:09, Pavel Stehule wrote:
>
>
>
> pá 24. 1. 2020 v 9:39 odesílatel Konstantin Knizhnik <
> k.knizhnik@postgrespro.ru> napsal:
>
>>
>>
>> On 23.01.2020 23:47, Robert Haas wrote:
>> > On Sat, Jan 11, 2020 at 8:51 PM Tomas Vondra
>> > <tomas.vondra@2ndquadrant.com> wrote:
>> >> I proposed just ignoring those new indexes because it seems much
>> simpler
>> >> than alternative solutions that I can think of, and it's not like those
>> >> other solutions don't have other issues.
>> > +1.
>> >
>> >> For example, I've looked at the "on demand" building as implemented in
>> >> global_private_temp-8.patch, I kinda doubt adding a bunch of index
>> build
>> >> calls into various places in index code seems somewht suspicious.
>> > +1. I can't imagine that's a safe or sane thing to do.
>> >
>>
>> As far as you know there are two versions of GTT implementations now.
>> And we are going to merge them into single patch.
>> But there are some principle question concerning provided functionality
>> which has to be be discussed:
>> should we prohibit DDL on GTT if there are more than one sessions using
>> it. It includes creation/dropping indexes, dropping table, altering
>> table...
>>
>> If the answer is "yes", then the question whether to populate new
>> indexes with data is no relevant at all, because such situation will not
>> be possible.
>> But in this case we will get incompatible behavior with normal
>> (permanent) tables and it seems to be very inconvenient from DBA point
>> of view:
>> it will be necessary to enforce all clients to close their sessions to
>> perform some DDL manipulations with GTT.
>> Some DDLs will be very difficult to implement if GTT is used by more
>> than one backend, for example altering table schema.
>>
>> My current solution is to allow creation/droping index on GTT and
>> dropping table itself, while prohibit alter schema at all for GTT.
>> Wenjing's approach is to prohibit any DDL if GTT is used by more than
>> one backend.
>>
>
> When I create index on GTT in one session, then I don't expect creating
> same index in all other sessions that uses same GTT.
>
> But I can imagine to creating index on GTT enforces index in current
> session, and for other sessions this index will be invalid to end of
> session.
>
>
> So there are three possible alternatives:
>
> 1. Prohibit index creation of GTT when it used by more than once session.
> 2. Create index and populate them with data in all sessions using this GTT.
> 3. Create index only in current session and do not allow to use it in all
> other sessions already using this GTT (but definitely allow to use it in
> new sessions).
>
> 1 is Wenjing's approach, 2 - is my approach, 3 - is your suggestion :)
>
> I can construct the following table with pro/cons of each approach:
>
> Approach
> Compatibility with normal table
> User (DBA) friendly
> Complexity of implementation
> Consistency
> 1
> -
> 1: requires restart of all sessions to perform operation
> 2: requires global cache of GTT
> 3*: *no man, no problem
> 2
> +
> 3: if index is created then it is actually needed, isn't it? 1: use
> existed functionality to create index
> 2: if alter schema is prohibited
> 3
> -
> 2: requires restart of all sessions to use created index
> 3: requires some mechanism for prohibiting index created after first
> session access to GTT
> 1: can perform DDL but do no see effect of it
>
>
You will see a effect of DDL in current session (where you did the change),
all other sessions should to live without any any change do reconnect or to
RESET connect
I don't like 2 - when I do index on global temp table, I don't would to
wait on indexing on all other sessions. These operations should be
maximally independent.
Regards
Pavel
>
>
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-24 13:17 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-01-24 13:17 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-24 19:39 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2020-01-24 19:39 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
pá 24. 1. 2020 v 14:17 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:
>
>
> On 24.01.2020 15:15, Pavel Stehule wrote:
>
> You will see a effect of DDL in current session (where you did the
> change), all other sessions should to live without any any change do
> reconnect or to RESET connect
>
> Why? I found this requirement quit unnatural and contradicting to the
> behavior of normal tables.
> Actually one of motivation for adding global tempo tables to Postgres is
> to provide compatibility with Oracle.
> Although I know that Oracle design decisions were never considered as
> axioms by Postgres community,
> but ni case of GTT design I think that we should take in account Oracle
> approach.
> And GTT in Oracle behaves exactly as in my implementation:
>
> https://www.oracletutorial.com/oracle-basics/oracle-global-temporary-table/
>
> It is not clear from this documentation whether index created for GTT in
> one session can be used in another session which already has some data in
> this GTT.
> But I did experiment with install Oracle server and can confirm that
> actually works in this way.
>
> So I do not understand why do we need to complicate our GTT implementation
> in order to prohibit useful functionality and introduce inconsistency
> between behavior of normal and global temp tables.
>
>
>
> I don't like 2 - when I do index on global temp table, I don't would to
> wait on indexing on all other sessions. These operations should be
> maximally independent.
>
>
> Nobody suggest to wait building index in all sessions.
> Indexes will be constructed on demand when session access this table.
> If session will no access this table at all, then index will never be
> constructed.
>
> Once again: logic of dealing with indexes in GTT is very simple.
> For normal tables, indexes are initialized at the tame when them are
> created.
> For GTT it is not true. We have to initialize index on demand when it is
> accessed first time in session.
>
> So it has to be handled in any way.
> The question is only whether we should allow creation of index for table
> already populated with some data?
> Actually doesn't require some additional efforts. We can use existed
> build_index function which initialize index and populates it with data.
> So the solution proposed for me is most natural, convenient and simplest
> solution at the same time. And compatible with Oracle.
>
I cannot to evaluate your proposal, and I am sure, so you know more about
this code.
There is a question if we can allow to build local temp index on global
temp table. It is different situation. When I work with global properties
personally I prefer total asynchronous implementation of any DDL operations
for other than current session. When it is true, then I have not any
objection. For me, good enough design of any DDL can be based on catalog
change without forcing to living tables.
I see following disadvantage of your proposal. See scenario
1. I have two sessions
A - small GTT with active owner
B - big GTT with some active application.
session A will do new index - it is fast, but if creating index is forced
on B on demand (when B was touched), then this operation have to wait after
index will be created.
So I afraid build a index on other sessions on GTT when GTT tables in other
sessions will not be empty.
Regards
Pavel
>
>
>
>
> 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
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-25 15:15 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-01-25 15:15 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Erik Rijkers <er@xs4all.nl>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
>
>
>
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com <http://www.postgrespro.com/;
> The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-27 09:11 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 2 replies; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-01-27 09:11 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-27 09:38 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-01-27 09:38 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Erik Rijkers <er@xs4all.nl>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-27 19:44 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
1 sibling, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2020-01-27 19:44 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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.
>
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-28 16:01 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-01-28 16:01 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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:
[application/octet-stream] global_temporary_table_v8-pg13.patch (153.9K, ../../9262B679-EA73-495E-82A6-508F5625023F@alibaba-inc.com/3-global_temporary_table_v8-pg13.patch)
download | inline diff:
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;
+
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-28 16:40 Pavel Stehule <pavel.stehule@gmail.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2020-01-28 16:40 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
út 28. 1. 2020 v 17:01 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:
>
>
> 2020年1月24日 上午4:47,Robert Haas <robertmhaas@gmail.com> 写道:
>
> On Sat, Jan 11, 2020 at 8:51 PM Tomas Vondra
> <tomas.vondra@2ndquadrant.com> wrote:
>
> I proposed just ignoring those new indexes because it seems much simpler
> than alternative solutions that I can think of, and it's not like those
> other solutions don't have other issues.
>
>
> +1.
>
> I complete the implementation of this feature.
> When a session x create an index idx_a on GTT A then
> For session x, idx_a is valid when after create index.
> For session y, before session x create index done, GTT A has some data,
> then index_a is invalid.
> For session z, before session x create index done, GTT A has no data,
> then index_a is valid.
>
>
> For example, I've looked at the "on demand" building as implemented in
> global_private_temp-8.patch, I kinda doubt adding a bunch of index build
> calls into various places in index code seems somewht suspicious.
>
>
> +1. I can't imagine that's a safe or sane thing to do.
>
> --
> Robert Haas
> EnterpriseDB: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>
>
> Opinion by Pavel
> + rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of
> field "rd_islocaltemp" is not probably best
> I renamed rd_islocaltemp
>
I don't see any change?
> Opinion by Konstantin Knizhnik
> 1 Fixed comments
> 2 Fixed assertion
>
>
> Please help me review.
>
>
> Wenjing
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-28 17:12 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 2 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-01-28 17:12 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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 Company
>
> Opinion by Pavel
> + rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of field "rd_islocaltemp" is not probably best
> I renamed rd_islocaltemp
>
> I don't see any change?
Rename rd_islocaltemp to rd_istemp in global_temporary_table_v8-pg13.patch
Wenjing
>
>
>
> Opinion by Konstantin Knizhnik
> 1 Fixed comments
> 2 Fixed assertion
>
>
> Please help me review.
>
>
> Wenjing
>
Attachments:
[application/octet-stream] global_temporary_table_v8-pg13.patch (153.9K, ../../8517A6EA-17A6-4160-8987-B01073A0ECAF@alibaba-inc.com/3-global_temporary_table_v8-pg13.patch)
download | inline diff:
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;
+
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-28 17:13 Pavel Stehule <pavel.stehule@gmail.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
1 sibling, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2020-01-28 17:13 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
út 28. 1. 2020 v 18:12 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:
>
>
> 2020年1月29日 上午12:40,Pavel Stehule <pavel.stehule@gmail.com> 写道:
>
>
>
> út 28. 1. 2020 v 17:01 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
> napsal:
>
>>
>>
>> 2020年1月24日 上午4:47,Robert Haas <robertmhaas@gmail.com> 写道:
>>
>> On Sat, Jan 11, 2020 at 8:51 PM Tomas Vondra
>> <tomas.vondra@2ndquadrant.com> wrote:
>>
>> I proposed just ignoring those new indexes because it seems much simpler
>> than alternative solutions that I can think of, and it's not like those
>> other solutions don't have other issues.
>>
>>
>> +1.
>>
>> I complete the implementation of this feature.
>> When a session x create an index idx_a on GTT A then
>> For session x, idx_a is valid when after create index.
>> For session y, before session x create index done, GTT A has some data,
>> then index_a is invalid.
>> For session z, before session x create index done, GTT A has no data,
>> then index_a is valid.
>>
>>
>> For example, I've looked at the "on demand" building as implemented in
>> global_private_temp-8.patch, I kinda doubt adding a bunch of index build
>> calls into various places in index code seems somewht suspicious.
>>
>>
>> +1. I can't imagine that's a safe or sane thing to do.
>>
>> --
>> Robert Haas
>> EnterpriseDB: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>>
>>
>> Opinion by Pavel
>> + rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of
>> field "rd_islocaltemp" is not probably best
>> I renamed rd_islocaltemp
>>
>
> I don't see any change?
>
> Rename rd_islocaltemp to rd_istemp
> in global_temporary_table_v8-pg13.patch
>
ok :)
Pavel
>
>
> Wenjing
>
>
>
>
>
>
>> Opinion by Konstantin Knizhnik
>> 1 Fixed comments
>> 2 Fixed assertion
>>
>>
>> Please help me review.
>>
>>
>> Wenjing
>>
>>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-28 17:54 Pavel Stehule <pavel.stehule@gmail.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 2 replies; 525+ messages in thread
From: Pavel Stehule @ 2020-01-28 17:54 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
út 28. 1. 2020 v 18:13 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:
>
>
> út 28. 1. 2020 v 18:12 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
> napsal:
>
>>
>>
>> 2020年1月29日 上午12:40,Pavel Stehule <pavel.stehule@gmail.com> 写道:
>>
>>
>>
>> út 28. 1. 2020 v 17:01 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
>> napsal:
>>
>>>
>>>
>>> 2020年1月24日 上午4:47,Robert Haas <robertmhaas@gmail.com> 写道:
>>>
>>> On Sat, Jan 11, 2020 at 8:51 PM Tomas Vondra
>>> <tomas.vondra@2ndquadrant.com> wrote:
>>>
>>> I proposed just ignoring those new indexes because it seems much simpler
>>> than alternative solutions that I can think of, and it's not like those
>>> other solutions don't have other issues.
>>>
>>>
>>> +1.
>>>
>>> I complete the implementation of this feature.
>>> When a session x create an index idx_a on GTT A then
>>> For session x, idx_a is valid when after create index.
>>> For session y, before session x create index done, GTT A has some data,
>>> then index_a is invalid.
>>> For session z, before session x create index done, GTT A has no data,
>>> then index_a is valid.
>>>
>>>
>>> For example, I've looked at the "on demand" building as implemented in
>>> global_private_temp-8.patch, I kinda doubt adding a bunch of index build
>>> calls into various places in index code seems somewht suspicious.
>>>
>>>
>>> +1. I can't imagine that's a safe or sane thing to do.
>>>
>>> --
>>> Robert Haas
>>> EnterpriseDB: http://www.enterprisedb.com
>>> The Enterprise PostgreSQL Company
>>>
>>>
>>> Opinion by Pavel
>>> + rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name
>>> of field "rd_islocaltemp" is not probably best
>>> I renamed rd_islocaltemp
>>>
>>
>> I don't see any change?
>>
>> Rename rd_islocaltemp to rd_istemp
>> in global_temporary_table_v8-pg13.patch
>>
>
> ok :)
>
I found a bug
postgres=# create global temp table x(a int);
CREATE TABLE
postgres=# insert into x values(1);
INSERT 0 1
postgres=# create index on x (a);
CREATE INDEX
postgres=# create index on x((a + 1));
CREATE INDEX
postgres=# analyze x;
WARNING: oid 16468 not a relation
ANALYZE
other behave looks well for me.
Regards
Pavel
> Pavel
>
>>
>>
>> Wenjing
>>
>>
>>
>>
>>
>>
>>> Opinion by Konstantin Knizhnik
>>> 1 Fixed comments
>>> 2 Fixed assertion
>>>
>>>
>>> Please help me review.
>>>
>>>
>>> Wenjing
>>>
>>>
>>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-29 08:12 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-01-29 08:12 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-29 13:06 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
1 sibling, 0 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-01-29 13:06 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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 Company
>>
>> Opinion by Pavel
>> + rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of field "rd_islocaltemp" is not probably best
>> I renamed rd_islocaltemp
>>
>> I don't see any change?
> Rename rd_islocaltemp to rd_istemp in global_temporary_table_v8-pg13.patch
>
> ok :)
>
> I found a bug
>
> postgres=# create global temp table x(a int);
> CREATE TABLE
> postgres=# insert into x values(1);
> INSERT 0 1
> postgres=# create index on x (a);
> CREATE INDEX
> postgres=# create index on x((a + 1));
> CREATE INDEX
> postgres=# analyze x;
> WARNING: oid 16468 not a relation
> ANALYZE
Thanks for review.
The index expression need to store statistics on index, I missed it and I'll fix it later.
Wenjing
>
> other behave looks well for me.
>
> Regards
>
> Pavel
>
>
> Pavel
>
>
> Wenjing
>
>
>
>>
>>
>>
>> Opinion by Konstantin Knizhnik
>> 1 Fixed comments
>> 2 Fixed assertion
>>
>>
>> Please help me review.
>>
>>
>> Wenjing
>>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-29 13:43 Robert Haas <robertmhaas@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
1 sibling, 0 replies; 525+ messages in thread
From: Robert Haas @ 2020-01-29 13:43 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-29 13:48 Robert Haas <robertmhaas@gmail.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
1 sibling, 1 reply; 525+ messages in thread
From: Robert Haas @ 2020-01-29 13:48 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On Tue, Jan 28, 2020 at 12:12 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:
>> Opinion by Pavel
>> + rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of field "rd_islocaltemp" is not probably best
>> I renamed rd_islocaltemp
>
> I don't see any change?
>
> Rename rd_islocaltemp to rd_istemp in global_temporary_table_v8-pg13.patch
In view of commit 6919b7e3294702adc39effd16634b2715d04f012, I think
that this has approximately a 0% chance of being acceptable. If you're
setting a field in a way that is inconsistent with the current use of
the field, you're probably doing it wrong, because the field has an
existing purpose to which new code must conform. And if you're not
doing that, then you don't need to rename it.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-29 14:47 Robert Haas <robertmhaas@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Robert Haas @ 2020-01-29 14:47 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-29 15:30 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 2 replies; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-01-29 15:30 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On 29.01.2020 17:47, Robert Haas wrote:
> On Wed, Jan 29, 2020 at 3:13 AM Konstantin Knizhnik
> <k.knizhnik@postgrespro.ru> wrote:
>> But I heard only two arguments:
>>
>> 1. Concurrent building of indexes by all backends may consume much memory (n_backends * maintenance_work_mem) and consume a lot of disk/CPU resources.
>> 2. GTT in one session can contains large amount of data and we need index for it, but small amount of data in another session and we do not need index for it.
> You seem to be ignoring the fact that two committers told you this
> probably wasn't safe.
>
> Perhaps your view is that those people made no argument, and therefore
> you don't have to respond to it. But the onus is not on somebody else
> to tell you why a completely novel idea is not safe. The onus is on
> you to analyze it in detail and prove that it is safe. What you need
> to show is that there is no code anywhere in the system which will be
> confused by an index springing into existence at whatever time you're
> creating it.
>
> One problem is that there are various backend-local data structures in
> the relcache, the planner, and the executor that remember information
> about indexes, and that may not respond well to having more indexes
> show up unexpectedly. On the one hand, they might crash; on the other
> hand, they might ignore the new index when they shouldn't. Another
> problem is that the code which creates indexes might fail or misbehave
> when run in an environment different from the one in which it
> currently runs. I haven't really studied your code, so I don't know
> exactly what it does, but for example it would be really bad to try to
> build an index while holding a buffer lock, both because it might
> cause (low-probability) undetected deadlocks and also because it might
> block another process that wants that buffer lock in a
> non-interruptible wait state for a long time.
>
> Now, maybe you can make an argument that you only create indexes at
> points in the query that are "safe." But I am skeptical, because of
> this example:
>
> rhaas=# create table foo (a int primary key, b text, c text, d text);
> CREATE TABLE
> rhaas=# create function blump() returns trigger as $$begin create
> index on foo (b); return new; end$$ language plpgsql;
> CREATE FUNCTION
> rhaas=# create trigger thud before insert on foo execute function blump();
> CREATE TRIGGER
> rhaas=# insert into foo (a) select generate_series(1,10);
> ERROR: cannot CREATE INDEX "foo" because it is being used by active
> queries in this session
> CONTEXT: SQL statement "create index on foo (b)"
> PL/pgSQL function blump() line 1 at SQL statement
>
> That prohibition is there for some reason. Someone did not just decide
> to arbitrarily prohibit it. A CREATE INDEX command run in that context
> won't run afoul of many of the things that might be problems in other
> places -- e.g. there won't be a buffer lock held. Yet, despite the
> fact that a trigger context is safe for executing a wide variety of
> user-defined code, this particular operation is not allowed here. That
> is the sort of thing that should worry you.
>
> At any rate, even if this somehow were or could be made safe,
> on-the-fly index creation is a feature that cannot and should not be
> combined with a patch to implement global temporary tables. Surely, it
> will require a lot of study and work to get the details right. And so
> will GTT. As I said in the other email I wrote, this feature is hard
> enough without adding this kind of thing to it. There's a reason why I
> never got around to implementing this ten years ago when I did
> unlogged tables; I was intending that to be a precursor to the GTT
> work. I found that it was too hard and I gave up. I'm glad to see
> people trying again, but the idea that we can afford to add in extra
> features, or frankly that either of the dueling patches on this thread
> are close to committable, is just plain wrong.
>
Sorry, I really didn't consider statements containing word "probably" as
arguments.
But I agree with you: it is task of developer of new feature to prove
that proposed approach is safe rather than of reviewers to demonstrate
that it is unsafe.
Can I provide such proof now? I afraid that not.
But please consider two arguments:
1. Index for GTT in any case has to be initialized on demand. In case of
regular tables index is initialized at the moment of its creation. In
case of GTT it doesn't work.
So we should somehow detect that accessed index is not initialized and
perform lazy initialization of the index.
The only difference with the approach proposed by Pavel (allow index
for empty GTT but prohibit it for GTT filled with data) is whether we
also need to populate index with data or not.
I can imagine that implicit initialization of index in read-only query
(select) can be unsafe and cause some problems. I have not encountered
such problems yet after performing many tests with GTTs, but certainly I
have not covered all possible scenarios (not sure that it is possible at
all).
But I do not understand how populating index with data can add some
extra unsafety.
So I can not prove that building index for GTT on demand is safe, but it
is not more unsafe than initialization of index on demand which is
required in any case.
2. Actually I do not propose some completely new approach. I try to
provide behavior with is compatible with regular tables.
If you create index for regular table, then it can be used in all
sessions, right?
And all "various backend-local data structures in the relcache, the
planner, and the executor that remember information about indexes"
have to be properly updated. It is done using invalidation mechanism.
The same mechanism is used in case of DDL operations with GTT, because
we change system catalog.
So my point here is that creation index of GTT is almost the same as
creation of index for regular tables and the same mechanism will be used
to provide correctness of this operation.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-29 17:08 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
1 sibling, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2020-01-29 17:08 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
> 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
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-29 17:21 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-01-29 17:21 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-29 17:37 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2020-01-29 17:37 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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.
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-29 18:16 Robert Haas <robertmhaas@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
1 sibling, 1 reply; 525+ messages in thread
From: Robert Haas @ 2020-01-29 18:16 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-30 08:45 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-01-30 08:45 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-30 09:23 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2020-01-30 09:23 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
č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
>
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-30 09:33 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-01-30 09:33 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-30 09:44 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-01-30 09:44 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-30 09:52 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2020-01-30 09:52 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
č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
> 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
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-30 10:02 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-01-30 10:02 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-30 10:10 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 0 replies; 525+ messages in thread
From: Pavel Stehule @ 2020-01-30 10:10 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
č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.
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-30 14:06 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
1 sibling, 0 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-01-30 14:06 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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 Company
>>
>> Opinion by Pavel
>> + rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of field "rd_islocaltemp" is not probably best
>> I renamed rd_islocaltemp
>>
>> I don't see any change?
> Rename rd_islocaltemp to rd_istemp in global_temporary_table_v8-pg13.patch
>
> ok :)
>
> I found a bug
>
> postgres=# create global temp table x(a int);
> CREATE TABLE
> postgres=# insert into x values(1);
> INSERT 0 1
> postgres=# create index on x (a);
> CREATE INDEX
> postgres=# create index on x((a + 1));
> CREATE INDEX
> postgres=# analyze x;
> WARNING: oid 16468 not a relation
> ANALYZE
The bug has been fixed in the global_temporary_table_v9-pg13.patch
Wenjing
>
> other behave looks well for me.
>
> Regards
>
> Pavel
>
>
> Pavel
>
>
> Wenjing
>
>
>
>>
>>
>>
>> Opinion by Konstantin Knizhnik
>> 1 Fixed comments
>> 2 Fixed assertion
>>
>>
>> Please help me review.
>>
>>
>> Wenjing
>>
>
Attachments:
[application/octet-stream] global_temporary_table_v9-pg13.patch (153.6K, ../../2A922659-0EBF-46E9-81BF-E5A3023B1421@alibaba-inc.com/3-global_temporary_table_v9-pg13.patch)
download | inline diff:
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;
+
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-30 14:17 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-01-30 14:17 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 2020年1月29日 下午9:48,Robert Haas <robertmhaas@gmail.com> 写道:
>
> On Tue, Jan 28, 2020 at 12:12 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:
>>> Opinion by Pavel
>>> + rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of field "rd_islocaltemp" is not probably best
>>> I renamed rd_islocaltemp
>>
>> I don't see any change?
>>
>> Rename rd_islocaltemp to rd_istemp in global_temporary_table_v8-pg13.patch
>
> In view of commit 6919b7e3294702adc39effd16634b2715d04f012, I think
> that this has approximately a 0% chance of being acceptable. If you're
> setting a field in a way that is inconsistent with the current use of
> the field, you're probably doing it wrong, because the field has an
> existing purpose to which new code must conform. And if you're not
> doing that, then you don't need to rename it.
Thank you for pointing it out.
I've rolled back the rename.
But I still need rd_localtemp to be true, The reason is that
1 GTT The GTT needs to support DML in read-only transactions ,like local temp table.
2 GTT does not need to hold the lock before modifying the index buffer ,also like local temp table.
Please give me feedback.
Wenjing
>
> --
> Robert Haas
> EnterpriseDB: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
Attachments:
[application/octet-stream] global_temporary_table_v9-pg13.patch (153.6K, ../../8BC7841E-3B32-4518-9586-D9ED571F4922@alibaba-inc.com/2-global_temporary_table_v9-pg13.patch)
download | inline diff:
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;
+
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-30 14:21 Pavel Stehule <pavel.stehule@gmail.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 2 replies; 525+ messages in thread
From: Pavel Stehule @ 2020-01-30 14:21 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
čt 30. 1. 2020 v 15:17 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:
>
>
> > 2020年1月29日 下午9:48,Robert Haas <robertmhaas@gmail.com> 写道:
> >
> > On Tue, Jan 28, 2020 at 12:12 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
> wrote:
> >>> Opinion by Pavel
> >>> + rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name
> of field "rd_islocaltemp" is not probably best
> >>> I renamed rd_islocaltemp
> >>
> >> I don't see any change?
> >>
> >> Rename rd_islocaltemp to rd_istemp in
> global_temporary_table_v8-pg13.patch
> >
> > In view of commit 6919b7e3294702adc39effd16634b2715d04f012, I think
> > that this has approximately a 0% chance of being acceptable. If you're
> > setting a field in a way that is inconsistent with the current use of
> > the field, you're probably doing it wrong, because the field has an
> > existing purpose to which new code must conform. And if you're not
> > doing that, then you don't need to rename it.
> Thank you for pointing it out.
> I've rolled back the rename.
> But I still need rd_localtemp to be true, The reason is that
> 1 GTT The GTT needs to support DML in read-only transactions ,like local
> temp table.
> 2 GTT does not need to hold the lock before modifying the index buffer
> ,also like local temp table.
>
> Please give me feedback.
>
maybe some like
rel->rd_globaltemp = true;
and somewhere else
if (rel->rd_localtemp || rel->rd_globaltemp)
{
...
}
>
> Wenjing
>
>
>
>
> >
> > --
> > Robert Haas
> > EnterpriseDB: http://www.enterprisedb.com
> > The Enterprise PostgreSQL Company
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-01-31 19:38 Robert Haas <robertmhaas@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Robert Haas @ 2020-01-31 19:38 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2020-02-01 11:49 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Philippe BEAUDOIN <phb07@apra.asso.fr>
0 siblings, 1 reply; 525+ messages in thread
From: Tomas Vondra @ 2020-02-01 11:49 UTC (permalink / raw)
To: Philippe BEAUDOIN <phb07@apra.asso.fr>; +Cc: pgsql-hackers@lists.postgresql.org, Konstantin Knizhnik <knizhnik@garret.ru>
Hi,
this patch was marked as waiting on author since the beginning of the
CF, most likely because it no longer applies (not sure). As there has
been very little activity since then, I've marked it as returned with
feedback. Feel free to re-submit an updated patch for 2020-03.
This definitely does not mean the feature is not desirable, but my
feeling is most of the discussion happens on the other thread dealing
with global temp tables [1] so maybe we should keep just that one and
combine the efforts.
[1] https://commitfest.postgresql.org/26/2349/
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-01 13:39 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
1 sibling, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-02-01 13:39 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 2020年1月30日 下午10:21,Pavel Stehule <pavel.stehule@gmail.com> 写道:
>
>
>
> čt 30. 1. 2020 v 15:17 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
>
>
> > 2020年1月29日 下午9:48,Robert Haas <robertmhaas@gmail.com <mailto:robertmhaas@gmail.com>> 写道:
> >
> > On Tue, Jan 28, 2020 at 12:12 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:
> >>> Opinion by Pavel
> >>> + rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of field "rd_islocaltemp" is not probably best
> >>> I renamed rd_islocaltemp
> >>
> >> I don't see any change?
> >>
> >> Rename rd_islocaltemp to rd_istemp in global_temporary_table_v8-pg13.patch
> >
> > In view of commit 6919b7e3294702adc39effd16634b2715d04f012, I think
> > that this has approximately a 0% chance of being acceptable. If you're
> > setting a field in a way that is inconsistent with the current use of
> > the field, you're probably doing it wrong, because the field has an
> > existing purpose to which new code must conform. And if you're not
> > doing that, then you don't need to rename it.
> Thank you for pointing it out.
> I've rolled back the rename.
> But I still need rd_localtemp to be true, The reason is that
> 1 GTT The GTT needs to support DML in read-only transactions ,like local temp table.
> 2 GTT does not need to hold the lock before modifying the index buffer ,also like local temp table.
>
> Please give me feedback.
>
> maybe some like
>
> rel->rd_globaltemp = true;
>
> and somewhere else
>
> if (rel->rd_localtemp || rel->rd_globaltemp)
> {
> ...
> }
I tried to optimize code in global_temporary_table_v10-pg13.patch
Please give me feedback.
Wenjing
>
>
>
> Wenjing
>
>
>
>
> >
> > --
> > Robert Haas
> > EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/;
> > The Enterprise PostgreSQL Company
>
Attachments:
[application/octet-stream] global_temporary_table_v10-pg13.patch (155.1K, ../../FE206F1E-0F94-4E60-9A1D-D52B1421F16B@alibaba-inc.com/3-global_temporary_table_v10-pg13.patch)
download | inline diff:
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;
+
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-01 16:14 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 2 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-02-01 16:14 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Erik Rijkers <er@xs4all.nl>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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.
>
>
>
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com <http://www.postgrespro.com/;
> The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-01 18:00 Pavel Stehule <pavel.stehule@gmail.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2020-02-01 18:00 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
so 1. 2. 2020 v 14:39 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:
>
>
> 2020年1月30日 下午10:21,Pavel Stehule <pavel.stehule@gmail.com> 写道:
>
>
>
> čt 30. 1. 2020 v 15:17 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
> napsal:
>
>>
>>
>> > 2020年1月29日 下午9:48,Robert Haas <robertmhaas@gmail.com> 写道:
>> >
>> > On Tue, Jan 28, 2020 at 12:12 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
>> wrote:
>> >>> Opinion by Pavel
>> >>> + rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the
>> name of field "rd_islocaltemp" is not probably best
>> >>> I renamed rd_islocaltemp
>> >>
>> >> I don't see any change?
>> >>
>> >> Rename rd_islocaltemp to rd_istemp in
>> global_temporary_table_v8-pg13.patch
>> >
>> > In view of commit 6919b7e3294702adc39effd16634b2715d04f012, I think
>> > that this has approximately a 0% chance of being acceptable. If you're
>> > setting a field in a way that is inconsistent with the current use of
>> > the field, you're probably doing it wrong, because the field has an
>> > existing purpose to which new code must conform. And if you're not
>> > doing that, then you don't need to rename it.
>> Thank you for pointing it out.
>> I've rolled back the rename.
>> But I still need rd_localtemp to be true, The reason is that
>> 1 GTT The GTT needs to support DML in read-only transactions ,like local
>> temp table.
>> 2 GTT does not need to hold the lock before modifying the index buffer
>> ,also like local temp table.
>>
>> Please give me feedback.
>>
>
> maybe some like
>
> rel->rd_globaltemp = true;
>
> and somewhere else
>
> if (rel->rd_localtemp || rel->rd_globaltemp)
> {
> ...
> }
>
> I tried to optimize code in global_temporary_table_v10-pg13.patch
>
>
> Please give me feedback.
>
I tested this patch and I have not any objections - from my user
perspective it is work as I expect
+#define RELATION_IS_TEMP(relation) \
+ ((relation)->rd_islocaltemp || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
It looks little bit unbalanced
maybe is better to inject rd_isglobaltemp to relation structure
and then
it should to like
+#define RELATION_IS_TEMP(relation) \
+ ((relation)->rd_islocaltemp || \
+ (relation)->rd_isglobaltemp))
But I have not idea if it helps in complex
> Wenjing
>
>
>
>
>
>>
>> Wenjing
>>
>>
>>
>>
>> >
>> > --
>> > Robert Haas
>> > EnterpriseDB: http://www.enterprisedb.com
>> > The Enterprise PostgreSQL Company
>>
>>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-03 08:08 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-02-03 08:08 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-03 08:16 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
1 sibling, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-02-03 08:16 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Erik Rijkers <er@xs4all.nl>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-03 13:03 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-02-03 13:03 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 2020年2月2日 上午2:00,Pavel Stehule <pavel.stehule@gmail.com> 写道:
>
>
>
> so 1. 2. 2020 v 14:39 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
>
>
>> 2020年1月30日 下午10:21,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:
>>
>>
>>
>> čt 30. 1. 2020 v 15:17 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
>>
>>
>> > 2020年1月29日 下午9:48,Robert Haas <robertmhaas@gmail.com <mailto:robertmhaas@gmail.com>> 写道:
>> >
>> > On Tue, Jan 28, 2020 at 12:12 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:
>> >>> Opinion by Pavel
>> >>> + rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of field "rd_islocaltemp" is not probably best
>> >>> I renamed rd_islocaltemp
>> >>
>> >> I don't see any change?
>> >>
>> >> Rename rd_islocaltemp to rd_istemp in global_temporary_table_v8-pg13.patch
>> >
>> > In view of commit 6919b7e3294702adc39effd16634b2715d04f012, I think
>> > that this has approximately a 0% chance of being acceptable. If you're
>> > setting a field in a way that is inconsistent with the current use of
>> > the field, you're probably doing it wrong, because the field has an
>> > existing purpose to which new code must conform. And if you're not
>> > doing that, then you don't need to rename it.
>> Thank you for pointing it out.
>> I've rolled back the rename.
>> But I still need rd_localtemp to be true, The reason is that
>> 1 GTT The GTT needs to support DML in read-only transactions ,like local temp table.
>> 2 GTT does not need to hold the lock before modifying the index buffer ,also like local temp table.
>>
>> Please give me feedback.
>>
>> maybe some like
>>
>> rel->rd_globaltemp = true;
>>
>> and somewhere else
>>
>> if (rel->rd_localtemp || rel->rd_globaltemp)
>> {
>> ...
>> }
> I tried to optimize code in global_temporary_table_v10-pg13.patch
>
>
> Please give me feedback.
>
> I tested this patch and I have not any objections - from my user perspective it is work as I expect
>
> +#define RELATION_IS_TEMP(relation) \
> + ((relation)->rd_islocaltemp || \
> + (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
>
> It looks little bit unbalanced
>
> maybe is better to inject rd_isglobaltemp to relation structure
>
> and then
>
> it should to like
>
> +#define RELATION_IS_TEMP(relation) \
> + ((relation)->rd_islocaltemp || \
> + (relation)->rd_isglobaltemp))
>
> But I have not idea if it helps in complex
In my opinion
For local temp table we need (relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP
and because one local temp table belongs to only one session, need to mark one sessions rd_islocaltemp = true ,and other to rd_islocaltemp = false.
But For GTT, just need (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_GLOBAL_TEMP
One GTT can be used for every session, so no need rd_isglobaltemp anymore. This seems duplicated and redundant.
>
>
>
>
>
>
>
> Wenjing
>
>
>
>>
>>
>>
>> Wenjing
>>
>>
>>
>>
>> >
>> > --
>> > Robert Haas
>> > EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/;
>> > The Enterprise PostgreSQL Company
>>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-03 19:46 Pavel Stehule <pavel.stehule@gmail.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 0 replies; 525+ messages in thread
From: Pavel Stehule @ 2020-02-03 19:46 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
po 3. 2. 2020 v 14:03 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:
>
>
> 2020年2月2日 上午2:00,Pavel Stehule <pavel.stehule@gmail.com> 写道:
>
>
>
> so 1. 2. 2020 v 14:39 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
> napsal:
>
>>
>>
>> 2020年1月30日 下午10:21,Pavel Stehule <pavel.stehule@gmail.com> 写道:
>>
>>
>>
>> čt 30. 1. 2020 v 15:17 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
>> napsal:
>>
>>>
>>>
>>> > 2020年1月29日 下午9:48,Robert Haas <robertmhaas@gmail.com> 写道:
>>> >
>>> > On Tue, Jan 28, 2020 at 12:12 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
>>> wrote:
>>> >>> Opinion by Pavel
>>> >>> + rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the
>>> name of field "rd_islocaltemp" is not probably best
>>> >>> I renamed rd_islocaltemp
>>> >>
>>> >> I don't see any change?
>>> >>
>>> >> Rename rd_islocaltemp to rd_istemp in
>>> global_temporary_table_v8-pg13.patch
>>> >
>>> > In view of commit 6919b7e3294702adc39effd16634b2715d04f012, I think
>>> > that this has approximately a 0% chance of being acceptable. If you're
>>> > setting a field in a way that is inconsistent with the current use of
>>> > the field, you're probably doing it wrong, because the field has an
>>> > existing purpose to which new code must conform. And if you're not
>>> > doing that, then you don't need to rename it.
>>> Thank you for pointing it out.
>>> I've rolled back the rename.
>>> But I still need rd_localtemp to be true, The reason is that
>>> 1 GTT The GTT needs to support DML in read-only transactions ,like local
>>> temp table.
>>> 2 GTT does not need to hold the lock before modifying the index buffer
>>> ,also like local temp table.
>>>
>>> Please give me feedback.
>>>
>>
>> maybe some like
>>
>> rel->rd_globaltemp = true;
>>
>> and somewhere else
>>
>> if (rel->rd_localtemp || rel->rd_globaltemp)
>> {
>> ...
>> }
>>
>> I tried to optimize code in global_temporary_table_v10-pg13.patch
>>
>>
>> Please give me feedback.
>>
>
> I tested this patch and I have not any objections - from my user
> perspective it is work as I expect
>
> +#define RELATION_IS_TEMP(relation) \
> + ((relation)->rd_islocaltemp || \
> + (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
>
> It looks little bit unbalanced
>
> maybe is better to inject rd_isglobaltemp to relation structure
>
> and then
>
> it should to like
>
> +#define RELATION_IS_TEMP(relation) \
> + ((relation)->rd_islocaltemp || \
> + (relation)->rd_isglobaltemp))
>
> But I have not idea if it helps in complex
>
> In my opinion
> For local temp table we need (relation)->rd_rel->relpersistence ==
> RELPERSISTENCE_TEMP
> and because one local temp table belongs to only one session, need to mark
> one sessions rd_islocaltemp = true ,and other to rd_islocaltemp = false.
>
so it means so table is assigned to current session or not. In this moment
I think so name "islocaltemp" is not the best - because there can be "local
temp table" that has this value false.
The name should be better describe so this table as attached to only
current session, and not for other, or is accessed by all session.
In this case I can understand why for GTT is possible to write
..rd_islocaltemp = true. But is signal so rd_islocaltemp is not good name
and rd_istemp is not good name too.
> But For GTT, just need (relation)->rd_rel->relpersistence ==
> RELPERSISTENCE_GLOBAL_GLOBAL_TEMP
> One GTT can be used for every session, so no need rd_isglobaltemp
> anymore. This seems duplicated and redundant.
>
I didn't understand well the sematic of rd_islocaltemp so my ideas in this
topics was not good. Now I think so rd_islocalname is not good name and can
be renamed if some body find better name. "istemptable" is not good too,
because there is important if relation is attached to the session or not.
>
>
>
>
>
>
>
>> Wenjing
>>
>>
>>
>>
>>
>>>
>>> Wenjing
>>>
>>>
>>>
>>>
>>> >
>>> > --
>>> > Robert Haas
>>> > EnterpriseDB: http://www.enterprisedb.com
>>> > The Enterprise PostgreSQL Company
>>>
>>>
>>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2020-02-03 20:56 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-02-03 20:56 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Philippe BEAUDOIN <phb07@apra.asso.fr>; +Cc: pgsql-hackers@lists.postgresql.org, Konstantin Knizhnik <knizhnik@garret.ru>
On 01.02.2020 14:49, Tomas Vondra wrote:
> Hi,
>
> this patch was marked as waiting on author since the beginning of the
> CF, most likely because it no longer applies (not sure). As there has
> been very little activity since then, I've marked it as returned with
> feedback. Feel free to re-submit an updated patch for 2020-03.
>
> This definitely does not mean the feature is not desirable, but my
> feeling is most of the discussion happens on the other thread dealing
> with global temp tables [1] so maybe we should keep just that one and
> combine the efforts.
>
> [1] https://commitfest.postgresql.org/26/2349/
>
New version of the patch with new method of GTT index construction is
attached.
Now GTT indexes are checked before query execution and are initialized
using AM build method.
So now GTT is supported for all indexes, including custom indexes.
--
Konstantin Knizhnik
Postgres Professional:http://www.postgrespro.com
The Russian Postgres Company
Attachments:
[text/x-patch] global_private_temp-9.patch (82.4K, ../../3e88b59f-73e8-685e-4983-9026f94c57c5@postgrespro.ru/2-global_private_temp-9.patch)
download | inline diff:
diff --git a/contrib/pg_prewarm/pg_prewarm.c b/contrib/pg_prewarm/pg_prewarm.c
index 33e2d28b27..93059ef581 100644
--- a/contrib/pg_prewarm/pg_prewarm.c
+++ b/contrib/pg_prewarm/pg_prewarm.c
@@ -178,7 +178,7 @@ pg_prewarm(PG_FUNCTION_ARGS)
for (block = first_block; block <= last_block; ++block)
{
CHECK_FOR_INTERRUPTS();
- smgrread(rel->rd_smgr, forkNumber, block, blockbuffer.data);
+ smgrread(rel->rd_smgr, forkNumber, block, blockbuffer.data, false);
++blocks_done;
}
}
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 79430d2b7b..39baddc743 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
+ },
+ false
+ },
/* 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,17 @@ 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/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 3fa4b766db..a86de5046f 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -670,6 +670,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/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acaed92..7c48e5c2ae 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -396,6 +396,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 8880586c37..22ce8953fd 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3707,7 +3707,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 fddfbf1d8c..97478352ea 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -92,6 +92,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;
@@ -367,7 +371,7 @@ RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
/* If we got a cancel signal during the copy of the data, quit */
CHECK_FOR_INTERRUPTS();
- smgrread(src, forkNum, blkno, buf.data);
+ smgrread(src, forkNum, blkno, buf.data, false);
if (!PageIsVerified(page, blkno))
ereport(ERROR,
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c9e6060035..1f5e52b54a 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1369,7 +1369,15 @@ LANGUAGE INTERNAL
STRICT STABLE PARALLEL SAFE
AS 'jsonb_path_query_first_tz';
+
+--
+-- Statistic for global temporary tables
--
+
+CREATE VIEW pg_gtt_statistic AS
+ SELECT s.* from pg_class c,pg_gtt_statistic_for_relation(c.oid) s where c.relpersistence='s';
+
+
-- The default permissions for functions mean that anyone can execute them.
-- A number of functions shouldn't be executable by just anyone, but rather
-- than use explicit 'superuser()' checks in those functions, we use the GRANT
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420ddd7f..85d8f04eeb 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -40,6 +40,7 @@
#include "commands/vacuum.h"
#include "executor/executor.h"
#include "foreign/fdwapi.h"
+#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_oper.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, 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);
@@ -323,6 +324,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,
@@ -586,14 +588,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);
}
/*
@@ -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, bool is_global_temp)
{
Relation sd;
int attno;
@@ -1558,30 +1560,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);
}
@@ -2890,3 +2904,72 @@ analyze_mcv_list(int *mcv_counts,
}
return num_mcv;
}
+
+PG_FUNCTION_INFO_V1(pg_gtt_statistic_for_relation);
+
+typedef struct
+{
+ int staattnum;
+ bool stainherit;
+} PgTempStatIteratorCtx;
+
+Datum
+pg_gtt_statistic_for_relation(PG_FUNCTION_ARGS)
+{
+ Oid starelid = PG_GETARG_OID(0);
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ TupleDesc tupdesc;
+ bool stainherit = false;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ /* Build tuplestore to hold the result rows */
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ /* Build a tuple descriptor for our result type */
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ do
+ {
+ int staattnum = 0;
+ while (true)
+ {
+ HeapTuple statup = SearchSysCacheCopy3(STATRELATTINH,
+ ObjectIdGetDatum(starelid),
+ Int16GetDatum(++staattnum),
+ BoolGetDatum(stainherit));
+ if (statup != NULL)
+ tuplestore_puttuple(tupstore, statup);
+ else
+ break;
+ }
+ stainherit = !stainherit;
+ } while (stainherit);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index e9d7a7ff79..a22a77aa5e 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,13 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
errmsg("cannot vacuum temporary tables of other sessions")));
}
+ /* not support cluster global temp table yet */
+ if (OldHeap->rd_rel->relpersistence == RELPERSISTENCE_SESSION)
+ 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.
@@ -1399,7 +1406,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 6aab73bfd4..bc3c986096 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 f599393473..1e4a52ee3f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12,6 +12,9 @@
*
*-------------------------------------------------------------------------
*/
+#include <sys/stat.h>
+#include <unistd.h>
+
#include "postgres.h"
#include "access/attmap.h"
@@ -555,6 +558,23 @@ static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+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;
+}
+
+
/* ----------------------------------------------------------------
* DefineRelation
* Creates a new relation.
@@ -598,6 +618,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
@@ -609,7 +630,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")));
@@ -634,17 +655,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
namespaceId =
RangeVarGetAndCheckCreationNamespace(stmt->relation, NoLock, NULL);
- /*
- * Security check: disallow creating temp tables from security-restricted
- * 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
- && InSecurityRestrictedOperation())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("cannot create temporary table within security-restricted operation")));
-
/*
* Determine the lockmode to use when scanning parents. A self-exclusive
* lock is needed here.
@@ -740,6 +750,38 @@ 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_SESSION)
+ {
+ 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 if (stmt->oncommit != ONCOMMIT_NOOP)
+ {
+ 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
+ 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 +1866,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);
@@ -3511,6 +3554,26 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
(void *) stmt);
}
+
+static bool
+CheckGlobalTempTableNotInUse(Relation rel)
+{
+ int id;
+ for (id = 1; id <= MaxBackends; id++)
+ {
+ if (id != MyBackendId)
+ {
+ struct stat fst;
+ char* path = relpathbackend(rel->rd_node, id, MAIN_FORKNUM);
+ int rc = stat(path, &fst);
+ pfree(path);
+ if (rc == 0 && fst.st_size != 0)
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* AlterTable
* Execute ALTER TABLE, which can be a list of subcommands
@@ -3568,6 +3631,9 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
rel = relation_open(context->relid, NoLock);
CheckTableNotInUse(rel, "ALTER TABLE");
+ if (rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION
+ && !CheckGlobalTempTableNotInUse(rel))
+ elog(ERROR, "Global temp table used by active backends can not be altered");
ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
}
@@ -8169,6 +8235,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,
@@ -14629,6 +14701,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 */
@@ -15116,14 +15195,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/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 8286d9cf34..7a12635e9c 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,6 +48,7 @@
#include "partitioning/partprune.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
+#include "utils/rel.h"
/* results of subquery_is_pushdown_safe */
@@ -618,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* the rest of the necessary infrastructure right now anyway. So
* for now, bail out if we see a temporary table.
*/
- if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+ if (IsLocalRelpersistence(get_rel_persistence(rte->relid)))
return;
/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d6f2153593..fd4e713646 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6312,7 +6312,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 (RelationHasSessionScope(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 d82fc5ab8b..95062ae344 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/index.h"
#include "catalog/pg_am.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_statistic_ext.h"
@@ -46,6 +47,7 @@
#include "rewrite/rewriteManip.h"
#include "statistics/statistics.h"
#include "storage/bufmgr.h"
+#include "storage/buf_internals.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/partcache.h"
@@ -80,6 +82,28 @@ static void set_baserel_partition_key_exprs(Relation relation,
static void set_baserel_partition_constraint(Relation relation,
RelOptInfo *rel);
+static bool
+is_index_valid(Relation index)
+{
+ if (!index->rd_index->indisvalid)
+ return false;
+
+ 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);
+ }
+ }
+ return true;
+}
/*
* get_relation_info -
@@ -205,7 +229,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
* still needs to insert into "invalid" indexes, if they're marked
* indisready.
*/
- if (!index->indisvalid)
+ if (!is_index_valid(indexRelation))
{
index_close(indexRelation, NoLock);
continue;
@@ -704,7 +728,7 @@ infer_arbiter_indexes(PlannerInfo *root)
idxRel = index_open(indexoid, rte->rellockmode);
idxForm = idxRel->rd_index;
- if (!idxForm->indisvalid)
+ if (!is_index_valid(idxRel))
goto next;
/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1b0edf5d3d..787de8329a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3288,20 +3288,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 ee2d2b54a1..e7f3a20fc4 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->sequence = makeRangeVar(snamespace, sname, -1);
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
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6d1f28c327..4074344030 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2152,7 +2152,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/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index aba3960481..6322984c45 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -429,7 +429,7 @@ ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref)
)
-static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence,
+static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence, char relkind,
ForkNumber forkNum, BlockNumber blockNum,
ReadBufferMode mode, BufferAccessStrategy strategy,
bool *hit);
@@ -663,7 +663,7 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
* miss.
*/
pgstat_count_buffer_read(reln);
- buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence,
+ buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence, reln->rd_rel->relkind,
forkNum, blockNum, mode, strategy, &hit);
if (hit)
pgstat_count_buffer_hit(reln);
@@ -691,7 +691,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
Assert(InRecovery);
- return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum,
+ return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, RELKIND_RELATION, forkNum, blockNum,
mode, strategy, &hit);
}
@@ -702,7 +702,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
* *hit is set to true if the request was satisfied from shared buffer cache.
*/
static Buffer
-ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
+ReadBuffer_common(SMgrRelation smgr, char relpersistence, char relkind, ForkNumber forkNum,
BlockNumber blockNum, ReadBufferMode mode,
BufferAccessStrategy strategy, bool *hit)
{
@@ -895,7 +895,8 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
if (track_io_timing)
INSTR_TIME_SET_CURRENT(io_start);
- smgrread(smgr, forkNum, blockNum, (char *) bufBlock);
+ smgrread(smgr, forkNum, blockNum, (char *) bufBlock,
+ relkind == RELKIND_INDEX);
if (track_io_timing)
{
@@ -2943,7 +2944,7 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber *forkNum,
/* If it's a local relation, it's localbuf.c's problem. */
if (RelFileNodeBackendIsTemp(rnode))
{
- if (rnode.backend == MyBackendId)
+ if (GetRelationBackendId(rnode.backend) == MyBackendId)
{
for (j = 0; j < nforks; j++)
DropRelFileNodeLocalBuffers(rnode.node, forkNum[j],
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index c5b771c531..4400b211f8 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -27,12 +27,14 @@
#include "access/xlog.h"
#include "access/xlogutils.h"
+#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "miscadmin.h"
#include "pg_trace.h"
#include "pgstat.h"
#include "postmaster/bgwriter.h"
#include "storage/bufmgr.h"
+#include "storage/ipc.h"
#include "storage/fd.h"
#include "storage/md.h"
#include "storage/relfilenode.h"
@@ -40,6 +42,7 @@
#include "storage/sync.h"
#include "utils/hsearch.h"
#include "utils/memutils.h"
+#include "utils/rel.h"
/*
* The magnetic disk storage manager keeps track of open file
@@ -87,6 +90,19 @@ 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;
+ ForkNumber forknum;
+ 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 +168,60 @@ 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, rel->forknum, 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, ForkNumber forknum)
+{
+ 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->forknum = forknum;
+ rel->next = SessionRelations;
+ SessionRelations = rel;
+}
+
+static void
+RegisterOnCommitAction(SMgrRelation reln, ForkNumber forknum)
+{
+ if (reln->smgr_owner && forknum == MAIN_FORKNUM)
+ {
+ Relation rel = (Relation)((char*)reln->smgr_owner - offsetof(RelationData, rd_smgr));
+ if (rel->rd_options
+ && ((StdRdOptions *)rel->rd_options)->on_commit_delete_rows)
+ {
+ register_on_commit_action(rel->rd_id, ONCOMMIT_DELETE_ROWS);
+ }
+ }
+}
+
/*
* mdexists() -- Does the physical file exist?
*
@@ -218,6 +288,8 @@ mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo)
errmsg("could not create file \"%s\": %m", path)));
}
}
+ if (RelFileNodeBackendIsGlobalTemp(reln->smgr_rnode))
+ RegisterSessionRelation(reln, forkNum);
pfree(path);
@@ -465,6 +537,21 @@ 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, forknum);
+ if (!(behavior & EXTENSION_RETURN_NULL))
+ RegisterOnCommitAction(reln, forknum);
+ goto NewSegment;
+ }
+ }
if ((behavior & EXTENSION_RETURN_NULL) &&
FILE_POSSIBLY_DELETED(errno))
{
@@ -476,6 +563,7 @@ mdopenfork(SMgrRelation reln, ForkNumber forknum, int behavior)
errmsg("could not open file \"%s\": %m", path)));
}
+ NewSegment:
pfree(path);
_fdvec_resize(reln, forknum, 1);
@@ -599,7 +687,7 @@ mdwriteback(SMgrRelation reln, ForkNumber forknum,
*/
void
mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
- char *buffer)
+ char *buffer, bool skipInit)
{
off_t seekpos;
int nbytes;
@@ -644,8 +732,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) && !skipInit)
+ mdwrite(reln, forknum, blocknum, buffer, true);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
@@ -735,7 +828,8 @@ mdnblocks(SMgrRelation reln, ForkNumber forknum)
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/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c
index 360b5bf5bf..a7b491b8d5 100644
--- a/src/backend/storage/smgr/smgr.c
+++ b/src/backend/storage/smgr/smgr.c
@@ -52,7 +52,7 @@ typedef struct f_smgr
void (*smgr_prefetch) (SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum);
void (*smgr_read) (SMgrRelation reln, ForkNumber forknum,
- BlockNumber blocknum, char *buffer);
+ BlockNumber blocknum, char *buffer, bool skipInit);
void (*smgr_write) (SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum, char *buffer, bool skipFsync);
void (*smgr_writeback) (SMgrRelation reln, ForkNumber forknum,
@@ -506,9 +506,9 @@ smgrprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum)
*/
void
smgrread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
- char *buffer)
+ char *buffer, bool skipInit)
{
- smgrsw[reln->smgr_which].smgr_read(reln, forknum, blocknum, buffer);
+ smgrsw[reln->smgr_which].smgr_read(reln, forknum, blocknum, buffer, skipInit);
}
/*
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 840664429e..0416549679 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 64776e3209..4996d8855c 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1191,6 +1191,110 @@ 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;
+
+ /*
+ * 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 df025a5a30..dd0b1ff32f 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1092,6 +1092,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;
@@ -3303,6 +3307,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 53d9ddf159..f263b8318c 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -1156,6 +1156,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/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index b7eee3da1d..afe22b2b9f 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -18,6 +18,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_statistic_d.h"
#include "funcapi.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_coerce.h"
@@ -30,6 +31,13 @@
#include "utils/syscache.h"
#include "utils/typcache.h"
+/*
+ * TODO: find less ugly way to declare core function returning pg_statistics.
+ * OID of pg_gtt_statistic_for_relation. This function should be handled in special way because it returns set of pg_statistics
+ * which contains attributes of anyarray type. Type of attributes can not be deduced from input parameters and
+ * it prevents using tuple descriptor in this case.
+ */
+#define GttStatisticFunctionId 3434
static void shutdown_MultiFuncCall(Datum arg);
static TypeFuncClass internal_get_result_type(Oid funcid,
@@ -341,7 +349,8 @@ internal_get_result_type(Oid funcid,
if (resolve_polymorphic_tupdesc(tupdesc,
&procform->proargtypes,
- call_expr))
+ call_expr) ||
+ funcid == GttStatisticFunctionId)
{
if (tupdesc->tdtypeid == RECORDOID &&
tupdesc->tdtypmod < 0)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ec3e2c63b0..4c15822d5c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15635,8 +15635,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 ad733d1363..be38d1728b 100644
--- a/src/common/relpath.c
+++ b/src/common/relpath.c
@@ -169,7 +169,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 a12fc1fc46..89c3645c39 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/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2228256907..6757491d35 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5503,7 +5503,14 @@
proname => 'pg_stat_get_xact_function_self_time', provolatile => 'v',
proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
prosrc => 'pg_stat_get_xact_function_self_time' },
-
+{ oid => '3434',
+ descr => 'show local statistics for global temp table',
+ proname => 'pg_gtt_statistic_for_relation', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '100', proargtypes => 'oid',
+ proallargtypes => '{oid,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,o,o,o,o,o,o,o,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,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,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_gtt_statistic_for_relation' },
{ oid => '3788',
descr => 'statistics: timestamp of the current statistics snapshot',
proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/storage/backendid.h b/src/include/storage/backendid.h
index 0c776a3e6c..124fc3c8fb 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,12 @@ extern PGDLLIMPORT BackendId ParallelMasterBackendId;
#define BackendIdForTempRelations() \
(ParallelMasterBackendId == InvalidBackendId ? MyBackendId : ParallelMasterBackendId)
+
+#define BackendIdForSessionRelations() \
+ (BackendIdForTempRelations() + SessionRelFirstBackendId)
+
+#define IsSessionRelationBackendId(id) ((id) >= SessionRelFirstBackendId)
+
+#define GetRelationBackendId(id) ((id) & ~SessionRelFirstBackendId)
+
#endif /* BACKENDID_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683a05..7ecef10e41 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -228,6 +228,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/md.h b/src/include/storage/md.h
index ec7630ce3b..56838831b7 100644
--- a/src/include/storage/md.h
+++ b/src/include/storage/md.h
@@ -31,7 +31,7 @@ extern void mdextend(SMgrRelation reln, ForkNumber forknum,
extern void mdprefetch(SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum);
extern void mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
- char *buffer);
+ char *buffer, bool skipInit);
extern void mdwrite(SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum, char *buffer, bool skipFsync);
extern void mdwriteback(SMgrRelation reln, ForkNumber forknum,
diff --git a/src/include/storage/relfilenode.h b/src/include/storage/relfilenode.h
index 4de9fc1e69..c45040b768 100644
--- a/src/include/storage/relfilenode.h
+++ b/src/include/storage/relfilenode.h
@@ -75,9 +75,24 @@ 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
diff --git a/src/include/storage/smgr.h b/src/include/storage/smgr.h
index 243822137c..a4a2da2e0b 100644
--- a/src/include/storage/smgr.h
+++ b/src/include/storage/smgr.h
@@ -95,7 +95,7 @@ extern void smgrextend(SMgrRelation reln, ForkNumber forknum,
extern void smgrprefetch(SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum);
extern void smgrread(SMgrRelation reln, ForkNumber forknum,
- BlockNumber blocknum, char *buffer);
+ BlockNumber blocknum, char *buffer, bool skipInit);
extern void smgrwrite(SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum, char *buffer, bool skipFsync);
extern void smgrwriteback(SMgrRelation reln, ForkNumber forknum,
diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h
index f4aa316604..365b02a9ba 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 44ed04dd3f..ae56427cba 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
@@ -332,6 +333,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
{
@@ -340,6 +353,7 @@ typedef enum ViewOptCheckOption
VIEW_OPTION_CHECK_OPTION_CASCADED
} ViewOptCheckOption;
+
/*
* ViewOptions
* Contents of rd_options for views
@@ -535,7 +549,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 f27b73d76d..eaf21d987d 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 0000000000..6114f8c091
--- /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 a2fa19230d..ef7aa85706 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 0000000000..5e95dd6f85
--- /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 0000000000..ae1adb6673
--- /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/rules.out b/src/test/regress/expected/rules.out
index 2ab2115fa1..7538601870 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,40 @@ 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_statistic| SELECT s.starelid,
+ s.staattnum,
+ s.stainherit,
+ s.stanullfrac,
+ s.stawidth,
+ s.stadistinct,
+ s.stakind1,
+ s.stakind2,
+ s.stakind3,
+ s.stakind4,
+ s.stakind5,
+ s.staop1,
+ s.staop2,
+ s.staop3,
+ s.staop4,
+ s.staop5,
+ s.stacoll1,
+ s.stacoll2,
+ s.stacoll3,
+ s.stacoll4,
+ s.stacoll5,
+ s.stanumbers1,
+ s.stanumbers2,
+ s.stanumbers3,
+ s.stanumbers4,
+ s.stanumbers5,
+ s.stavalues1,
+ s.stavalues2,
+ s.stavalues3,
+ s.stavalues4,
+ s.stavalues5
+ FROM pg_class c,
+ LATERAL pg_gtt_statistic_for_relation(c.oid) 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 = 's'::"char");
pg_hba_file_rules| SELECT a.line_number,
a.type,
a.database,
diff --git a/src/test/regress/expected/session_table.out b/src/test/regress/expected/session_table.out
new file mode 100644
index 0000000000..1b9b3f4d20
--- /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 d2b17dd3ea..71c8ca4f20 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 acba391332..71abe08e4e 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 0000000000..3058b9b2c1
--- /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 0000000000..c6663dc89b
--- /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;
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-04 15:01 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-02-04 15:01 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Erik Rijkers <er@xs4all.nl>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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.
https://www.postgresql.org/message-id/CA%2BTgmob%3DL1k0cpXRcipdsaE07ok%2BOn%3DtTjRiw7FtD_D2T%3DJwhg%... <https://www.postgresql.org/message-id/CA+Tgmob=L1k0cpXRcipdsaE07ok+On=tTjRiw7FtD_D2T=Jwhg@mail.gmail...;
>
> --
> Konstantin Knizhnik
> Postgres Professional: http://www.postgrespro.com <http://www.postgrespro.com/;
> The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-04 16:47 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-02-04 16:47 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Erik Rijkers <er@xs4all.nl>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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.
> https://www.postgresql.org/message-id/CA%2BTgmob%3DL1k0cpXRcipdsaE07ok%2BOn%3DtTjRiw7FtD_D2T%3DJwhg%...
> <https://www.postgresql.org/message-id/CA+Tgmob=L1k0cpXRcipdsaE07ok+On=tTjRiw7FtD_D2T=Jwhg@mail.gmail...;
>
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.
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-04 20:57 Robert Haas <robertmhaas@gmail.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
1 sibling, 1 reply; 525+ messages in thread
From: Robert Haas @ 2020-02-04 20:57 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Pavel Stehule <pavel.stehule@gmail.com>; Erik Rijkers <er@xs4all.nl>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-04 21:38 Robert Haas <robertmhaas@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Robert Haas @ 2020-02-04 21:38 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-05 07:28 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-02-05 07:28 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
https://www.postgresql.org/message-id/3e88b59f-73e8-685e-4983-9026f94c57c5%40postgrespro.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.
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-05 09:51 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 0 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-02-05 09:51 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Pavel Stehule <pavel.stehule@gmail.com>; Erik Rijkers <er@xs4all.nl>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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
>
> --
> Robert Haas
> EnterpriseDB: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-05 13:20 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-02-05 13:20 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Erik Rijkers <er@xs4all.nl>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 2020年2月5日 上午12:47,Konstantin Knizhnik <k.knizhnik@postgrespro.ru> 写道:
>
>
>
> On 04.02.2020 18:01, 曾文旌(义从) wrote:
>>
>>
>>>
>>> Yes, exactly.
>>> But it is still not clear to me why do we need some special handling for GTT?
>>> Shared memory is reinitialized and storage of temporary tables is removed.
>>> It is true for both local and global temp tables.
>> Of course not. The local temp table cleans up the entire table (including catalog buffer and datafile). GTT is not.
>>
>
> What do you mean by "catalog buffer"?
> Yes, cleanup of local temp table requires deletion of correspondent entry from catalog and GTT should not do it.
> But I am speaking only about cleanup of data files of temp relations. It is done in the same way for local and global temp tables.
For native pg, the data file of temp table will not be cleaned up direct after oom happen.
Because the orphan local temp table(include catalog, local buffer, datafile) will be cleaned up by deleting the orphan temp schame in autovacuum.
So for GTT ,we cannot do the same with just deleting data files. This is why I dealt with it specifically.
>
>
>>> In my patch autovacuum is prohibited for GTT.
>> But vacuum GTT is not prohibited.
>>
> Yes, but the simplest solution is to prohibit also explicit vacuum of GTT, isn't it?
>
>>>
>>>> IMHO forced terminated of client sessions is not acceptable solution.
>>>>> And it is not an absolutely necessary requirement.
>>>>> So from my point of view we should not add such limitations to GTT design.
>>>> This limitation makes it possible for the GTT to do all the DDL.
>>>> IMHO even oracle's GTT has similar limitations.
>>>
>>> I have checked that Oracle is not preventing creation of index for GTT if there are some active sessions working with this table. And this index becomes visible for all this sessions.
>> 1 Yes The creation of inde gtt has been improved in global_temporary_table_v10-pg13.patch
>> 2 But alter GTT ; drop GTT ; drop index on GTT is blocked by other sessions
>>
> Yes, you are right.
> Orale documetation says:
> > 1) DDL operation on global temporary tables
> > It is not possible to perform a DDL operation (except TRUNCATE <https://www.oracletutorial.com/oracle-basics/oracle-truncate-table/;) 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.
>>>
>>> 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.
>> https://www.postgresql.org/message-id/CA%2BTgmob%3DL1k0cpXRcipdsaE07ok%2BOn%3DtTjRiw7FtD_D2T%3DJwhg%... <https://www.postgresql.org/message-id/CA+Tgmob=L1k0cpXRcipdsaE07ok+On=tTjRiw7FtD_D2T=Jwhg@mail.gmail...;
>>
>
> 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.
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-05 14:10 Robert Haas <robertmhaas@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Robert Haas @ 2020-02-05 14:10 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-05 14:15 Robert Haas <robertmhaas@gmail.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 1 reply; 525+ messages in thread
From: Robert Haas @ 2020-02-05 14:15 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Pavel Stehule <pavel.stehule@gmail.com>; Erik Rijkers <er@xs4all.nl>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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?
> 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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-05 15:48 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 2 replies; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-02-05 15:48 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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.
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-05 19:18 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
1 sibling, 0 replies; 525+ messages in thread
From: Pavel Stehule @ 2020-02-05 19:18 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
>
>
> >
> > 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.
>
>
>
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-06 03:39 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 0 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-02-06 03:39 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Pavel Stehule <pavel.stehule@gmail.com>; Erik Rijkers <er@xs4all.nl>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Julien Rouhaud <rjuju123@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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
>
> 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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-07 15:15 Robert Haas <robertmhaas@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
1 sibling, 1 reply; 525+ messages in thread
From: Robert Haas @ 2020-02-07 15:15 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-07 17:28 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 2 replies; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-02-07 17:28 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2020-02-07 17:31 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-02-07 17:31 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Philippe BEAUDOIN <phb07@apra.asso.fr>; +Cc: pgsql-hackers@lists.postgresql.org, Konstantin Knizhnik <knizhnik@garret.ru>
Fix GTT index initialization.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
[text/x-patch] global_private_temp-10.patch (83.7K, ../../cc294a73-8eac-3622-7ccd-528d010d8bb8@postgrespro.ru/2-global_private_temp-10.patch)
download | inline diff:
diff --git a/contrib/pg_prewarm/pg_prewarm.c b/contrib/pg_prewarm/pg_prewarm.c
index 33e2d28..93059ef 100644
--- a/contrib/pg_prewarm/pg_prewarm.c
+++ b/contrib/pg_prewarm/pg_prewarm.c
@@ -178,7 +178,7 @@ pg_prewarm(PG_FUNCTION_ARGS)
for (block = first_block; block <= last_block; ++block)
{
CHECK_FOR_INTERRUPTS();
- smgrread(rel->rd_smgr, forkNumber, block, blockbuffer.data);
+ smgrread(rel->rd_smgr, forkNumber, block, blockbuffer.data, false);
++blocks_done;
}
}
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 79430d2..39baddc 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
+ },
+ false
+ },
/* 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,17 @@ 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/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 3fa4b76..a86de50 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -670,6 +670,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/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..7c48e5c 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -396,6 +396,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 8880586..22ce895 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3707,7 +3707,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 fddfbf1..9747835 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -92,6 +92,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;
@@ -367,7 +371,7 @@ RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
/* If we got a cancel signal during the copy of the data, quit */
CHECK_FOR_INTERRUPTS();
- smgrread(src, forkNum, blkno, buf.data);
+ smgrread(src, forkNum, blkno, buf.data, false);
if (!PageIsVerified(page, blkno))
ereport(ERROR,
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c9e6060..1f5e52b 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1369,7 +1369,15 @@ LANGUAGE INTERNAL
STRICT STABLE PARALLEL SAFE
AS 'jsonb_path_query_first_tz';
+
+--
+-- Statistic for global temporary tables
--
+
+CREATE VIEW pg_gtt_statistic AS
+ SELECT s.* from pg_class c,pg_gtt_statistic_for_relation(c.oid) s where c.relpersistence='s';
+
+
-- The default permissions for functions mean that anyone can execute them.
-- A number of functions shouldn't be executable by just anyone, but rather
-- than use explicit 'superuser()' checks in those functions, we use the GRANT
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..85d8f04 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -40,6 +40,7 @@
#include "commands/vacuum.h"
#include "executor/executor.h"
#include "foreign/fdwapi.h"
+#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_oper.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, 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);
@@ -323,6 +324,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,
@@ -586,14 +588,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);
}
/*
@@ -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, bool is_global_temp)
{
Relation sd;
int attno;
@@ -1558,30 +1560,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);
}
@@ -2890,3 +2904,72 @@ analyze_mcv_list(int *mcv_counts,
}
return num_mcv;
}
+
+PG_FUNCTION_INFO_V1(pg_gtt_statistic_for_relation);
+
+typedef struct
+{
+ int staattnum;
+ bool stainherit;
+} PgTempStatIteratorCtx;
+
+Datum
+pg_gtt_statistic_for_relation(PG_FUNCTION_ARGS)
+{
+ Oid starelid = PG_GETARG_OID(0);
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ TupleDesc tupdesc;
+ bool stainherit = false;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ /* Build tuplestore to hold the result rows */
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ /* Build a tuple descriptor for our result type */
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ do
+ {
+ int staattnum = 0;
+ while (true)
+ {
+ HeapTuple statup = SearchSysCacheCopy3(STATRELATTINH,
+ ObjectIdGetDatum(starelid),
+ Int16GetDatum(++staattnum),
+ BoolGetDatum(stainherit));
+ if (statup != NULL)
+ tuplestore_puttuple(tupstore, statup);
+ else
+ break;
+ }
+ stainherit = !stainherit;
+ } while (stainherit);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index e9d7a7f..a22a77a 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,13 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
errmsg("cannot vacuum temporary tables of other sessions")));
}
+ /* not support cluster global temp table yet */
+ if (OldHeap->rd_rel->relpersistence == RELPERSISTENCE_SESSION)
+ 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.
@@ -1399,7 +1406,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 6aab73b..bc3c986 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 f599393..1e4a52e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12,6 +12,9 @@
*
*-------------------------------------------------------------------------
*/
+#include <sys/stat.h>
+#include <unistd.h>
+
#include "postgres.h"
#include "access/attmap.h"
@@ -555,6 +558,23 @@ static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+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;
+}
+
+
/* ----------------------------------------------------------------
* DefineRelation
* Creates a new relation.
@@ -598,6 +618,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
@@ -609,7 +630,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")));
@@ -635,17 +656,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
RangeVarGetAndCheckCreationNamespace(stmt->relation, NoLock, NULL);
/*
- * Security check: disallow creating temp tables from security-restricted
- * 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
- && InSecurityRestrictedOperation())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("cannot create temporary table within security-restricted operation")));
-
- /*
* Determine the lockmode to use when scanning parents. A self-exclusive
* lock is needed here.
*
@@ -740,6 +750,38 @@ 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_SESSION)
+ {
+ 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 if (stmt->oncommit != ONCOMMIT_NOOP)
+ {
+ 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
+ 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 +1866,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);
@@ -3511,6 +3554,26 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
(void *) stmt);
}
+
+static bool
+CheckGlobalTempTableNotInUse(Relation rel)
+{
+ int id;
+ for (id = 1; id <= MaxBackends; id++)
+ {
+ if (id != MyBackendId)
+ {
+ struct stat fst;
+ char* path = relpathbackend(rel->rd_node, id, MAIN_FORKNUM);
+ int rc = stat(path, &fst);
+ pfree(path);
+ if (rc == 0 && fst.st_size != 0)
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* AlterTable
* Execute ALTER TABLE, which can be a list of subcommands
@@ -3568,6 +3631,9 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
rel = relation_open(context->relid, NoLock);
CheckTableNotInUse(rel, "ALTER TABLE");
+ if (rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION
+ && !CheckGlobalTempTableNotInUse(rel))
+ elog(ERROR, "Global temp table used by active backends can not be altered");
ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
}
@@ -8169,6 +8235,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,
@@ -14629,6 +14701,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 */
@@ -15116,14 +15195,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/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 59d1a31..0442565 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -354,6 +354,7 @@ ExecInsert(ModifyTableState *mtstate,
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
OnConflictAction onconflict = node->onConflictAction;
+ if (resultRelInfo->ri_NumIndices > 0)
ExecMaterializeSlot(slot);
/*
@@ -361,7 +362,14 @@ ExecInsert(ModifyTableState *mtstate,
*/
resultRelInfo = estate->es_result_relation_info;
resultRelationDesc = resultRelInfo->ri_RelationDesc;
-
+ if (resultRelationDesc->rd_rel->relpersistence == RELPERSISTENCE_SESSION)
+ {
+ int i;
+ for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+ {
+ InitGTTIndexes(resultRelInfo->ri_IndexRelationDescs[i]);
+ }
+ }
/*
* BEFORE ROW INSERT Triggers.
*
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 8286d9c..7a12635 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,6 +48,7 @@
#include "partitioning/partprune.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
+#include "utils/rel.h"
/* results of subquery_is_pushdown_safe */
@@ -618,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* the rest of the necessary infrastructure right now anyway. So
* for now, bail out if we see a temporary table.
*/
- if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+ if (IsLocalRelpersistence(get_rel_persistence(rte->relid)))
return;
/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d6f2153..fd4e713 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6312,7 +6312,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 (RelationHasSessionScope(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 d82fc5a..619ed96 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -80,6 +80,15 @@ static void set_baserel_partition_key_exprs(Relation relation,
static void set_baserel_partition_constraint(Relation relation,
RelOptInfo *rel);
+static bool
+is_index_valid(Relation index)
+{
+ if (!index->rd_index->indisvalid)
+ return false;
+ if (index->rd_rel->relpersistence == RELPERSISTENCE_SESSION)
+ InitGTTIndexes(index);
+ return true;
+}
/*
* get_relation_info -
@@ -205,7 +214,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
* still needs to insert into "invalid" indexes, if they're marked
* indisready.
*/
- if (!index->indisvalid)
+ if (!is_index_valid(indexRelation))
{
index_close(indexRelation, NoLock);
continue;
@@ -704,7 +713,7 @@ infer_arbiter_indexes(PlannerInfo *root)
idxRel = index_open(indexoid, rte->rellockmode);
idxForm = idxRel->rd_index;
- if (!idxForm->indisvalid)
+ if (!is_index_valid(idxRel))
goto next;
/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1b0edf5..787de83 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3288,20 +3288,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 ee2d2b5..e7f3a20 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -438,6 +438,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 6d1f28c..4074344 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2152,7 +2152,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/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index aba3960..2d88cc9 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -33,9 +33,11 @@
#include <sys/file.h>
#include <unistd.h>
+#include "access/amapi.h"
#include "access/tableam.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
+#include "catalog/index.h"
#include "catalog/storage.h"
#include "executor/instrument.h"
#include "lib/binaryheap.h"
@@ -429,7 +431,7 @@ ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref)
)
-static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence,
+static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence, char relkind,
ForkNumber forkNum, BlockNumber blockNum,
ReadBufferMode mode, BufferAccessStrategy strategy,
bool *hit);
@@ -663,7 +665,7 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
* miss.
*/
pgstat_count_buffer_read(reln);
- buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence,
+ buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence, reln->rd_rel->relkind,
forkNum, blockNum, mode, strategy, &hit);
if (hit)
pgstat_count_buffer_hit(reln);
@@ -691,7 +693,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
Assert(InRecovery);
- return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum,
+ return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, RELKIND_RELATION, forkNum, blockNum,
mode, strategy, &hit);
}
@@ -702,7 +704,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
* *hit is set to true if the request was satisfied from shared buffer cache.
*/
static Buffer
-ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
+ReadBuffer_common(SMgrRelation smgr, char relpersistence, char relkind, ForkNumber forkNum,
BlockNumber blockNum, ReadBufferMode mode,
BufferAccessStrategy strategy, bool *hit)
{
@@ -895,7 +897,8 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
if (track_io_timing)
INSTR_TIME_SET_CURRENT(io_start);
- smgrread(smgr, forkNum, blockNum, (char *) bufBlock);
+ smgrread(smgr, forkNum, blockNum, (char *) bufBlock,
+ relkind == RELKIND_INDEX);
if (track_io_timing)
{
@@ -2943,7 +2946,7 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber *forkNum,
/* If it's a local relation, it's localbuf.c's problem. */
if (RelFileNodeBackendIsTemp(rnode))
{
- if (rnode.backend == MyBackendId)
+ if (GetRelationBackendId(rnode.backend) == MyBackendId)
{
for (j = 0; j < nforks; j++)
DropRelFileNodeLocalBuffers(rnode.node, forkNum[j],
@@ -4423,3 +4426,19 @@ TestForOldSnapshot_impl(Snapshot snapshot, Relation relation)
(errcode(ERRCODE_SNAPSHOT_TOO_OLD),
errmsg("snapshot too old")));
}
+
+void InitGTTIndexes(Relation index)
+{
+ Buffer metapage = ReadBuffer(index, 0);
+ bool isNew = PageIsNew(BufferGetPage(metapage));
+ Assert(index->rd_rel->relpersistence == RELPERSISTENCE_SESSION);
+ 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);
+ }
+}
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index c5b771c..4400b21 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -27,12 +27,14 @@
#include "access/xlog.h"
#include "access/xlogutils.h"
+#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "miscadmin.h"
#include "pg_trace.h"
#include "pgstat.h"
#include "postmaster/bgwriter.h"
#include "storage/bufmgr.h"
+#include "storage/ipc.h"
#include "storage/fd.h"
#include "storage/md.h"
#include "storage/relfilenode.h"
@@ -40,6 +42,7 @@
#include "storage/sync.h"
#include "utils/hsearch.h"
#include "utils/memutils.h"
+#include "utils/rel.h"
/*
* The magnetic disk storage manager keeps track of open file
@@ -87,6 +90,19 @@ 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;
+ ForkNumber forknum;
+ 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 +168,60 @@ 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, rel->forknum, 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, ForkNumber forknum)
+{
+ 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->forknum = forknum;
+ rel->next = SessionRelations;
+ SessionRelations = rel;
+}
+
+static void
+RegisterOnCommitAction(SMgrRelation reln, ForkNumber forknum)
+{
+ if (reln->smgr_owner && forknum == MAIN_FORKNUM)
+ {
+ Relation rel = (Relation)((char*)reln->smgr_owner - offsetof(RelationData, rd_smgr));
+ if (rel->rd_options
+ && ((StdRdOptions *)rel->rd_options)->on_commit_delete_rows)
+ {
+ register_on_commit_action(rel->rd_id, ONCOMMIT_DELETE_ROWS);
+ }
+ }
+}
+
/*
* mdexists() -- Does the physical file exist?
*
@@ -218,6 +288,8 @@ mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo)
errmsg("could not create file \"%s\": %m", path)));
}
}
+ if (RelFileNodeBackendIsGlobalTemp(reln->smgr_rnode))
+ RegisterSessionRelation(reln, forkNum);
pfree(path);
@@ -465,6 +537,21 @@ 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, forknum);
+ if (!(behavior & EXTENSION_RETURN_NULL))
+ RegisterOnCommitAction(reln, forknum);
+ goto NewSegment;
+ }
+ }
if ((behavior & EXTENSION_RETURN_NULL) &&
FILE_POSSIBLY_DELETED(errno))
{
@@ -476,6 +563,7 @@ mdopenfork(SMgrRelation reln, ForkNumber forknum, int behavior)
errmsg("could not open file \"%s\": %m", path)));
}
+ NewSegment:
pfree(path);
_fdvec_resize(reln, forknum, 1);
@@ -599,7 +687,7 @@ mdwriteback(SMgrRelation reln, ForkNumber forknum,
*/
void
mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
- char *buffer)
+ char *buffer, bool skipInit)
{
off_t seekpos;
int nbytes;
@@ -644,8 +732,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) && !skipInit)
+ mdwrite(reln, forknum, blocknum, buffer, true);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
@@ -735,7 +828,8 @@ mdnblocks(SMgrRelation reln, ForkNumber forknum)
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/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c
index 360b5bf..a7b491b 100644
--- a/src/backend/storage/smgr/smgr.c
+++ b/src/backend/storage/smgr/smgr.c
@@ -52,7 +52,7 @@ typedef struct f_smgr
void (*smgr_prefetch) (SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum);
void (*smgr_read) (SMgrRelation reln, ForkNumber forknum,
- BlockNumber blocknum, char *buffer);
+ BlockNumber blocknum, char *buffer, bool skipInit);
void (*smgr_write) (SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum, char *buffer, bool skipFsync);
void (*smgr_writeback) (SMgrRelation reln, ForkNumber forknum,
@@ -506,9 +506,9 @@ smgrprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum)
*/
void
smgrread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
- char *buffer)
+ char *buffer, bool skipInit)
{
- smgrsw[reln->smgr_which].smgr_read(reln, forknum, blocknum, buffer);
+ smgrsw[reln->smgr_which].smgr_read(reln, forknum, blocknum, buffer, skipInit);
}
/*
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0416549 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 64776e3..4996d88 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1191,6 +1191,110 @@ 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;
+
+ /*
+ * 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 df025a5..dd0b1ff 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1092,6 +1092,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;
@@ -3303,6 +3307,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 53d9ddf..f263b83 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -1156,6 +1156,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/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index b7eee3d..afe22b2 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -18,6 +18,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_statistic_d.h"
#include "funcapi.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_coerce.h"
@@ -30,6 +31,13 @@
#include "utils/syscache.h"
#include "utils/typcache.h"
+/*
+ * TODO: find less ugly way to declare core function returning pg_statistics.
+ * OID of pg_gtt_statistic_for_relation. This function should be handled in special way because it returns set of pg_statistics
+ * which contains attributes of anyarray type. Type of attributes can not be deduced from input parameters and
+ * it prevents using tuple descriptor in this case.
+ */
+#define GttStatisticFunctionId 3434
static void shutdown_MultiFuncCall(Datum arg);
static TypeFuncClass internal_get_result_type(Oid funcid,
@@ -341,7 +349,8 @@ internal_get_result_type(Oid funcid,
if (resolve_polymorphic_tupdesc(tupdesc,
&procform->proargtypes,
- call_expr))
+ call_expr) ||
+ funcid == GttStatisticFunctionId)
{
if (tupdesc->tdtypeid == RECORDOID &&
tupdesc->tdtypmod < 0)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ec3e2c6..4c15822 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15635,8 +15635,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 ad733d1..be38d17 100644
--- a/src/common/relpath.c
+++ b/src/common/relpath.c
@@ -169,7 +169,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 a12fc1f..89c3645 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/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2228256..6757491 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5503,7 +5503,14 @@
proname => 'pg_stat_get_xact_function_self_time', provolatile => 'v',
proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
prosrc => 'pg_stat_get_xact_function_self_time' },
-
+{ oid => '3434',
+ descr => 'show local statistics for global temp table',
+ proname => 'pg_gtt_statistic_for_relation', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '100', proargtypes => 'oid',
+ proallargtypes => '{oid,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,o,o,o,o,o,o,o,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,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,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_gtt_statistic_for_relation' },
{ oid => '3788',
descr => 'statistics: timestamp of the current statistics snapshot',
proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/storage/backendid.h b/src/include/storage/backendid.h
index 0c776a3..124fc3c 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,12 @@ extern PGDLLIMPORT BackendId ParallelMasterBackendId;
#define BackendIdForTempRelations() \
(ParallelMasterBackendId == InvalidBackendId ? MyBackendId : ParallelMasterBackendId)
+
+#define BackendIdForSessionRelations() \
+ (BackendIdForTempRelations() + SessionRelFirstBackendId)
+
+#define IsSessionRelationBackendId(id) ((id) >= SessionRelFirstBackendId)
+
+#define GetRelationBackendId(id) ((id) & ~SessionRelFirstBackendId)
+
#endif /* BACKENDID_H */
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 73c7e9b..467134c 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -231,6 +231,7 @@ extern void TestForOldSnapshot_impl(Snapshot snapshot, Relation relation);
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
+extern void InitGTTIndexes(Relation rel);
/* inline functions */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..7ecef10 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/md.h b/src/include/storage/md.h
index ec7630c..5683883 100644
--- a/src/include/storage/md.h
+++ b/src/include/storage/md.h
@@ -31,7 +31,7 @@ extern void mdextend(SMgrRelation reln, ForkNumber forknum,
extern void mdprefetch(SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum);
extern void mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
- char *buffer);
+ char *buffer, bool skipInit);
extern void mdwrite(SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum, char *buffer, bool skipFsync);
extern void mdwriteback(SMgrRelation reln, ForkNumber forknum,
diff --git a/src/include/storage/relfilenode.h b/src/include/storage/relfilenode.h
index 4de9fc1..c45040b 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/storage/smgr.h b/src/include/storage/smgr.h
index 2438221..a4a2da2 100644
--- a/src/include/storage/smgr.h
+++ b/src/include/storage/smgr.h
@@ -95,7 +95,7 @@ extern void smgrextend(SMgrRelation reln, ForkNumber forknum,
extern void smgrprefetch(SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum);
extern void smgrread(SMgrRelation reln, ForkNumber forknum,
- BlockNumber blocknum, char *buffer);
+ BlockNumber blocknum, char *buffer, bool skipInit);
extern void smgrwrite(SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum, char *buffer, bool skipFsync);
extern void smgrwriteback(SMgrRelation reln, ForkNumber forknum,
diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h
index f4aa316..365b02a 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 44ed04d..ae56427 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
@@ -332,6 +333,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
{
@@ -340,6 +353,7 @@ typedef enum ViewOptCheckOption
VIEW_OPTION_CHECK_OPTION_CASCADED
} ViewOptCheckOption;
+
/*
* ViewOptions
* Contents of rd_options for views
@@ -535,7 +549,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 f27b73d..eaf21d9 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/rules.out b/src/test/regress/expected/rules.out
index 2ab2115..7538601 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,40 @@ 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_statistic| SELECT s.starelid,
+ s.staattnum,
+ s.stainherit,
+ s.stanullfrac,
+ s.stawidth,
+ s.stadistinct,
+ s.stakind1,
+ s.stakind2,
+ s.stakind3,
+ s.stakind4,
+ s.stakind5,
+ s.staop1,
+ s.staop2,
+ s.staop3,
+ s.staop4,
+ s.staop5,
+ s.stacoll1,
+ s.stacoll2,
+ s.stacoll3,
+ s.stacoll4,
+ s.stacoll5,
+ s.stanumbers1,
+ s.stanumbers2,
+ s.stanumbers3,
+ s.stanumbers4,
+ s.stanumbers5,
+ s.stavalues1,
+ s.stavalues2,
+ s.stavalues3,
+ s.stavalues4,
+ s.stavalues5
+ FROM pg_class c,
+ LATERAL pg_gtt_statistic_for_relation(c.oid) 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 = 's'::"char");
pg_hba_file_rules| SELECT a.line_number,
a.type,
a.database,
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 d2b17dd..71c8ca4 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 acba391..71abe08 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;
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-07 17:31 Robert Haas <robertmhaas@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
1 sibling, 0 replies; 525+ messages in thread
From: Robert Haas @ 2020-02-07 17:31 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-07 18:37 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
1 sibling, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2020-02-07 18:37 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
pá 7. 2. 2020 v 18:28 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:
>
>
> On 07.02.2020 18:15, Robert Haas wrote:
> > On Wed, Feb 5, 2020 at 10:48 AM Konstantin Knizhnik
> > <k.knizhnik@postgrespro.ru> wrote:
> > My answer is - yes.
> >> Just because:
> >> - Such behavior is compatible with regular tables. So it will not
> >> confuse users and doesn't require some complex explanations.
> >> - It is compatible with Oracle.
> >> - It is what DBA usually want when creating index.
> >> -
> >> There are several arguments against such behavior:
> >> - Concurrent building of index in multiple sessions can consume a lot of
> >> memory
> >> - Building index can increase query execution time (which can be not
> >> expected by clients)
> > I think those are good arguments, especially the second one. There's
> > no limit on how long building a new index might take, and it could be
> > several minutes. A user who was running a query that could have
> > completed in a few seconds or even milliseconds will be unhappy to
> > suddenly wait a long time for a new index to be built. And that is an
> > entirely realistic scenario, because the new index might be better,
> > but only marginally.
> Yes, I agree that this arguments are important.
> But IMHO less important than incompatible behavior (Pavel doesn't agree
> with word "incompatible" in this context
> since semantic of temp tables is in any case different with semantic of
> regular tables).
>
> Just want to notice that if we have huge GTT (so that creation of index
> takes significant amount of time)
> sequential scan of this table also will not be fast.
>
> But in any case, if we agree that we can control thus behavior using GUC
> or index property,
> then it is ok for me.
>
>
>
> >
> > Also, an important point to which I've already alluded a few times is
> > that creating an index can fail. Now, one way it can fail is that
> > there could be some problem writing to disk, or you could run out of
> > memory, or whatever. However, it can also fail because the new index
> > is UNIQUE and the data this backend has in the table doesn't conform
> > to the associated constraint. It will be confusing if all access to a
> > table suddenly starts complaining about uniqueness violations.
>
> Yes, building index can fail (as any other operation with database).
> What's wring with it?
> If it is fatal error, then backend is terminated and content of its temp
> table is disappeared.
> If it is non-fatal error, then current transaction is aborted:
>
>
> Session1:
> postgres=# create global temp table gtt(x integer);
> CREATE TABLE
> postgres=# insert into gtt values (generate_series(1,100000));
> INSERT 0 100000
>
> Session2:
> postgres=# insert into gtt values (generate_series(1,100000));
> INSERT 0 100000
> postgres=# insert into gtt values (1);
> INSERT 0 1
>
What when session 2 has active transaction? Then to be correct, you should
to wait with index creation to end of transaction.
> Session1:
> postgres=# create unique index on gtt(x);
> CREATE INDEX
>
> Sessin2:
> postgres=# explain select * from gtt where x=1;
> ERROR: could not create unique index "gtt_x_idx"
> DETAIL: Key (x)=(1) is duplicated.
>
This is little bit unexpected behave (probably nobody expect so any SELECT
fail with error "could not create index" - I understand exactly to reason
and context, but this side effect is something what I afraid.
>
> > 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
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-09 12:05 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 2 replies; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-02-09 12:05 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On 07.02.2020 21:37, Pavel Stehule wrote:
>
> What when session 2 has active transaction? Then to be correct, you
> should to wait with index creation to end of transaction.
>
>
> Session1:
> postgres=# create unique index on gtt(x);
> CREATE INDEX
>
> Sessin2:
> postgres=# explain select * from gtt where x=1;
> ERROR: could not create unique index "gtt_x_idx"
> DETAIL: Key (x)=(1) is duplicated.
>
>
> This is little bit unexpected behave (probably nobody expect so any
> SELECT fail with error "could not create index" - I understand exactly
> to reason and context, but this side effect is something what I afraid.
>
The more I thinking creation of indexes for GTT on-demand, the more
contractions I see.
So looks like there are only two safe alternatives:
1. Allow DDL for GTT (including index creation) only if there are no
other sessions using this GTT ("using" means that no data was inserted
in GTT by this session). Things can be even more complicated if we take
in account inter-table dependencies (like foreign key constraint).
2. Create indexes for GTT locally.
2) seems to be very contradictory (global table metadata, but private
indexes) and hard to implement because in this case we have to maintain
some private copy of index catalog to keep information about private
indexes.
1) is currently implemented by Wenjing. Frankly speaking I still find
such limitation too restrictive and inconvenient for users. From my
point of view Oracle developers have implemented better compromise. But
if I am the only person voting for such solution, then let's stop this
discussion.
But in any case I think that calling ambuild to construct index for
empty table is better solution than implementation of all indexes (and
still not solving the problem with custom indexes).
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-09 12:53 Pavel Stehule <pavel.stehule@gmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
1 sibling, 0 replies; 525+ messages in thread
From: Pavel Stehule @ 2020-02-09 12:53 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
ne 9. 2. 2020 v 13:05 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:
>
>
> On 07.02.2020 21:37, Pavel Stehule wrote:
>
>
> What when session 2 has active transaction? Then to be correct, you should
> to wait with index creation to end of transaction.
>
>
>> Session1:
>> postgres=# create unique index on gtt(x);
>> CREATE INDEX
>>
>> Sessin2:
>> postgres=# explain select * from gtt where x=1;
>> ERROR: could not create unique index "gtt_x_idx"
>> DETAIL: Key (x)=(1) is duplicated.
>>
>
> This is little bit unexpected behave (probably nobody expect so any SELECT
> fail with error "could not create index" - I understand exactly to reason
> and context, but this side effect is something what I afraid.
>
>
> The more I thinking creation of indexes for GTT on-demand, the more
> contractions I see.
> So looks like there are only two safe alternatives:
> 1. Allow DDL for GTT (including index creation) only if there are no other
> sessions using this GTT ("using" means that no data was inserted in GTT by
> this session). Things can be even more complicated if we take in account
> inter-table dependencies (like foreign key constraint).
> 2. Create indexes for GTT locally.
>
> 2) seems to be very contradictory (global table metadata, but private
> indexes) and hard to implement because in this case we have to maintain
> some private copy of index catalog to keep information about private
> indexes.
>
> 1) is currently implemented by Wenjing. Frankly speaking I still find such
> limitation too restrictive and inconvenient for users. From my point of
> view Oracle developers have implemented better compromise. But if I am the
> only person voting for such solution, then let's stop this discussion.
>
Thank you. I respect your opinion.
> But in any case I think that calling ambuild to construct index for empty
> table is better solution than implementation of all indexes (and still not
> solving the problem with custom indexes).
>
I know nothing about this area - I expect so you and Wenjing will find
good solution.
We have to start with something what is simple, usable, and if it possible
it is well placed to Postgres's architecture.
at @1 .. when all tables are empty for other sessions, then I don't see any
problem. From practical reason, I think so requirement to don't use table
in other sessions is too hard, and I can be nice (maybe it is) if creating
index should not be blocked, but if I create index too late, then index is
for other session (when the table is used) invalid (again it can be done in
future).
I am sure, so there are not end of all days - and there is a space for
future enhancing and testing other variants. I can imagine more different
variations with different advantages/disadvantages. Just for begin I prefer
design that has concept closer to current Postgres.
Regards
Pavel
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2020-02-10 16:48 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-02-10 16:48 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Philippe BEAUDOIN <phb07@apra.asso.fr>; +Cc: pgsql-hackers@lists.postgresql.org, Konstantin Knizhnik <knizhnik@garret.ru>
Sorry, small typo in the last patch.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
[text/x-patch] global_private_temp-11.patch (83.4K, ../../fe1b5260-965b-acf6-ad0d-01f1ed34b006@postgrespro.ru/2-global_private_temp-11.patch)
download | inline diff:
diff --git a/contrib/pg_prewarm/pg_prewarm.c b/contrib/pg_prewarm/pg_prewarm.c
index 33e2d28..93059ef 100644
--- a/contrib/pg_prewarm/pg_prewarm.c
+++ b/contrib/pg_prewarm/pg_prewarm.c
@@ -178,7 +178,7 @@ pg_prewarm(PG_FUNCTION_ARGS)
for (block = first_block; block <= last_block; ++block)
{
CHECK_FOR_INTERRUPTS();
- smgrread(rel->rd_smgr, forkNumber, block, blockbuffer.data);
+ smgrread(rel->rd_smgr, forkNumber, block, blockbuffer.data, false);
++blocks_done;
}
}
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 79430d2..39baddc 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
+ },
+ false
+ },
/* 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,17 @@ 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/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 3fa4b76..a86de50 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -670,6 +670,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/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..7c48e5c 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -396,6 +396,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 8880586..22ce895 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3707,7 +3707,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 fddfbf1..9747835 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -92,6 +92,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;
@@ -367,7 +371,7 @@ RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
/* If we got a cancel signal during the copy of the data, quit */
CHECK_FOR_INTERRUPTS();
- smgrread(src, forkNum, blkno, buf.data);
+ smgrread(src, forkNum, blkno, buf.data, false);
if (!PageIsVerified(page, blkno))
ereport(ERROR,
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c9e6060..1f5e52b 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1369,7 +1369,15 @@ LANGUAGE INTERNAL
STRICT STABLE PARALLEL SAFE
AS 'jsonb_path_query_first_tz';
+
+--
+-- Statistic for global temporary tables
--
+
+CREATE VIEW pg_gtt_statistic AS
+ SELECT s.* from pg_class c,pg_gtt_statistic_for_relation(c.oid) s where c.relpersistence='s';
+
+
-- The default permissions for functions mean that anyone can execute them.
-- A number of functions shouldn't be executable by just anyone, but rather
-- than use explicit 'superuser()' checks in those functions, we use the GRANT
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..85d8f04 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -40,6 +40,7 @@
#include "commands/vacuum.h"
#include "executor/executor.h"
#include "foreign/fdwapi.h"
+#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_oper.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, 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);
@@ -323,6 +324,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,
@@ -586,14 +588,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);
}
/*
@@ -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, bool is_global_temp)
{
Relation sd;
int attno;
@@ -1558,30 +1560,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);
}
@@ -2890,3 +2904,72 @@ analyze_mcv_list(int *mcv_counts,
}
return num_mcv;
}
+
+PG_FUNCTION_INFO_V1(pg_gtt_statistic_for_relation);
+
+typedef struct
+{
+ int staattnum;
+ bool stainherit;
+} PgTempStatIteratorCtx;
+
+Datum
+pg_gtt_statistic_for_relation(PG_FUNCTION_ARGS)
+{
+ Oid starelid = PG_GETARG_OID(0);
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ TupleDesc tupdesc;
+ bool stainherit = false;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ /* Build tuplestore to hold the result rows */
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ /* Build a tuple descriptor for our result type */
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ do
+ {
+ int staattnum = 0;
+ while (true)
+ {
+ HeapTuple statup = SearchSysCacheCopy3(STATRELATTINH,
+ ObjectIdGetDatum(starelid),
+ Int16GetDatum(++staattnum),
+ BoolGetDatum(stainherit));
+ if (statup != NULL)
+ tuplestore_puttuple(tupstore, statup);
+ else
+ break;
+ }
+ stainherit = !stainherit;
+ } while (stainherit);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index e9d7a7f..a22a77a 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,13 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
errmsg("cannot vacuum temporary tables of other sessions")));
}
+ /* not support cluster global temp table yet */
+ if (OldHeap->rd_rel->relpersistence == RELPERSISTENCE_SESSION)
+ 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.
@@ -1399,7 +1406,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 6aab73b..bc3c986 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 f599393..1e4a52e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12,6 +12,9 @@
*
*-------------------------------------------------------------------------
*/
+#include <sys/stat.h>
+#include <unistd.h>
+
#include "postgres.h"
#include "access/attmap.h"
@@ -555,6 +558,23 @@ static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+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;
+}
+
+
/* ----------------------------------------------------------------
* DefineRelation
* Creates a new relation.
@@ -598,6 +618,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
@@ -609,7 +630,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")));
@@ -635,17 +656,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
RangeVarGetAndCheckCreationNamespace(stmt->relation, NoLock, NULL);
/*
- * Security check: disallow creating temp tables from security-restricted
- * 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
- && InSecurityRestrictedOperation())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("cannot create temporary table within security-restricted operation")));
-
- /*
* Determine the lockmode to use when scanning parents. A self-exclusive
* lock is needed here.
*
@@ -740,6 +750,38 @@ 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_SESSION)
+ {
+ 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 if (stmt->oncommit != ONCOMMIT_NOOP)
+ {
+ 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
+ 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 +1866,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);
@@ -3511,6 +3554,26 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
(void *) stmt);
}
+
+static bool
+CheckGlobalTempTableNotInUse(Relation rel)
+{
+ int id;
+ for (id = 1; id <= MaxBackends; id++)
+ {
+ if (id != MyBackendId)
+ {
+ struct stat fst;
+ char* path = relpathbackend(rel->rd_node, id, MAIN_FORKNUM);
+ int rc = stat(path, &fst);
+ pfree(path);
+ if (rc == 0 && fst.st_size != 0)
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* AlterTable
* Execute ALTER TABLE, which can be a list of subcommands
@@ -3568,6 +3631,9 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
rel = relation_open(context->relid, NoLock);
CheckTableNotInUse(rel, "ALTER TABLE");
+ if (rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION
+ && !CheckGlobalTempTableNotInUse(rel))
+ elog(ERROR, "Global temp table used by active backends can not be altered");
ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
}
@@ -8169,6 +8235,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,
@@ -14629,6 +14701,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 */
@@ -15116,14 +15195,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/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 59d1a31..fe3283a 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -361,7 +361,14 @@ ExecInsert(ModifyTableState *mtstate,
*/
resultRelInfo = estate->es_result_relation_info;
resultRelationDesc = resultRelInfo->ri_RelationDesc;
-
+ if (resultRelationDesc->rd_rel->relpersistence == RELPERSISTENCE_SESSION)
+ {
+ int i;
+ for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+ {
+ InitGTTIndexes(resultRelInfo->ri_IndexRelationDescs[i]);
+ }
+ }
/*
* BEFORE ROW INSERT Triggers.
*
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 8286d9c..7a12635 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,6 +48,7 @@
#include "partitioning/partprune.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
+#include "utils/rel.h"
/* results of subquery_is_pushdown_safe */
@@ -618,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* the rest of the necessary infrastructure right now anyway. So
* for now, bail out if we see a temporary table.
*/
- if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+ if (IsLocalRelpersistence(get_rel_persistence(rte->relid)))
return;
/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d6f2153..fd4e713 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6312,7 +6312,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 (RelationHasSessionScope(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 d82fc5a..619ed96 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -80,6 +80,15 @@ static void set_baserel_partition_key_exprs(Relation relation,
static void set_baserel_partition_constraint(Relation relation,
RelOptInfo *rel);
+static bool
+is_index_valid(Relation index)
+{
+ if (!index->rd_index->indisvalid)
+ return false;
+ if (index->rd_rel->relpersistence == RELPERSISTENCE_SESSION)
+ InitGTTIndexes(index);
+ return true;
+}
/*
* get_relation_info -
@@ -205,7 +214,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
* still needs to insert into "invalid" indexes, if they're marked
* indisready.
*/
- if (!index->indisvalid)
+ if (!is_index_valid(indexRelation))
{
index_close(indexRelation, NoLock);
continue;
@@ -704,7 +713,7 @@ infer_arbiter_indexes(PlannerInfo *root)
idxRel = index_open(indexoid, rte->rellockmode);
idxForm = idxRel->rd_index;
- if (!idxForm->indisvalid)
+ if (!is_index_valid(idxRel))
goto next;
/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1b0edf5..787de83 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3288,20 +3288,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 ee2d2b5..e7f3a20 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -438,6 +438,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 6d1f28c..4074344 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2152,7 +2152,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/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index aba3960..2d88cc9 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -33,9 +33,11 @@
#include <sys/file.h>
#include <unistd.h>
+#include "access/amapi.h"
#include "access/tableam.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
+#include "catalog/index.h"
#include "catalog/storage.h"
#include "executor/instrument.h"
#include "lib/binaryheap.h"
@@ -429,7 +431,7 @@ ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref)
)
-static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence,
+static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence, char relkind,
ForkNumber forkNum, BlockNumber blockNum,
ReadBufferMode mode, BufferAccessStrategy strategy,
bool *hit);
@@ -663,7 +665,7 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
* miss.
*/
pgstat_count_buffer_read(reln);
- buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence,
+ buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence, reln->rd_rel->relkind,
forkNum, blockNum, mode, strategy, &hit);
if (hit)
pgstat_count_buffer_hit(reln);
@@ -691,7 +693,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
Assert(InRecovery);
- return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum,
+ return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, RELKIND_RELATION, forkNum, blockNum,
mode, strategy, &hit);
}
@@ -702,7 +704,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
* *hit is set to true if the request was satisfied from shared buffer cache.
*/
static Buffer
-ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
+ReadBuffer_common(SMgrRelation smgr, char relpersistence, char relkind, ForkNumber forkNum,
BlockNumber blockNum, ReadBufferMode mode,
BufferAccessStrategy strategy, bool *hit)
{
@@ -895,7 +897,8 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
if (track_io_timing)
INSTR_TIME_SET_CURRENT(io_start);
- smgrread(smgr, forkNum, blockNum, (char *) bufBlock);
+ smgrread(smgr, forkNum, blockNum, (char *) bufBlock,
+ relkind == RELKIND_INDEX);
if (track_io_timing)
{
@@ -2943,7 +2946,7 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber *forkNum,
/* If it's a local relation, it's localbuf.c's problem. */
if (RelFileNodeBackendIsTemp(rnode))
{
- if (rnode.backend == MyBackendId)
+ if (GetRelationBackendId(rnode.backend) == MyBackendId)
{
for (j = 0; j < nforks; j++)
DropRelFileNodeLocalBuffers(rnode.node, forkNum[j],
@@ -4423,3 +4426,19 @@ TestForOldSnapshot_impl(Snapshot snapshot, Relation relation)
(errcode(ERRCODE_SNAPSHOT_TOO_OLD),
errmsg("snapshot too old")));
}
+
+void InitGTTIndexes(Relation index)
+{
+ Buffer metapage = ReadBuffer(index, 0);
+ bool isNew = PageIsNew(BufferGetPage(metapage));
+ Assert(index->rd_rel->relpersistence == RELPERSISTENCE_SESSION);
+ 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);
+ }
+}
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index c5b771c..4400b21 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -27,12 +27,14 @@
#include "access/xlog.h"
#include "access/xlogutils.h"
+#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "miscadmin.h"
#include "pg_trace.h"
#include "pgstat.h"
#include "postmaster/bgwriter.h"
#include "storage/bufmgr.h"
+#include "storage/ipc.h"
#include "storage/fd.h"
#include "storage/md.h"
#include "storage/relfilenode.h"
@@ -40,6 +42,7 @@
#include "storage/sync.h"
#include "utils/hsearch.h"
#include "utils/memutils.h"
+#include "utils/rel.h"
/*
* The magnetic disk storage manager keeps track of open file
@@ -87,6 +90,19 @@ 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;
+ ForkNumber forknum;
+ 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 +168,60 @@ 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, rel->forknum, 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, ForkNumber forknum)
+{
+ 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->forknum = forknum;
+ rel->next = SessionRelations;
+ SessionRelations = rel;
+}
+
+static void
+RegisterOnCommitAction(SMgrRelation reln, ForkNumber forknum)
+{
+ if (reln->smgr_owner && forknum == MAIN_FORKNUM)
+ {
+ Relation rel = (Relation)((char*)reln->smgr_owner - offsetof(RelationData, rd_smgr));
+ if (rel->rd_options
+ && ((StdRdOptions *)rel->rd_options)->on_commit_delete_rows)
+ {
+ register_on_commit_action(rel->rd_id, ONCOMMIT_DELETE_ROWS);
+ }
+ }
+}
+
/*
* mdexists() -- Does the physical file exist?
*
@@ -218,6 +288,8 @@ mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo)
errmsg("could not create file \"%s\": %m", path)));
}
}
+ if (RelFileNodeBackendIsGlobalTemp(reln->smgr_rnode))
+ RegisterSessionRelation(reln, forkNum);
pfree(path);
@@ -465,6 +537,21 @@ 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, forknum);
+ if (!(behavior & EXTENSION_RETURN_NULL))
+ RegisterOnCommitAction(reln, forknum);
+ goto NewSegment;
+ }
+ }
if ((behavior & EXTENSION_RETURN_NULL) &&
FILE_POSSIBLY_DELETED(errno))
{
@@ -476,6 +563,7 @@ mdopenfork(SMgrRelation reln, ForkNumber forknum, int behavior)
errmsg("could not open file \"%s\": %m", path)));
}
+ NewSegment:
pfree(path);
_fdvec_resize(reln, forknum, 1);
@@ -599,7 +687,7 @@ mdwriteback(SMgrRelation reln, ForkNumber forknum,
*/
void
mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
- char *buffer)
+ char *buffer, bool skipInit)
{
off_t seekpos;
int nbytes;
@@ -644,8 +732,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) && !skipInit)
+ mdwrite(reln, forknum, blocknum, buffer, true);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
@@ -735,7 +828,8 @@ mdnblocks(SMgrRelation reln, ForkNumber forknum)
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/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c
index 360b5bf..a7b491b 100644
--- a/src/backend/storage/smgr/smgr.c
+++ b/src/backend/storage/smgr/smgr.c
@@ -52,7 +52,7 @@ typedef struct f_smgr
void (*smgr_prefetch) (SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum);
void (*smgr_read) (SMgrRelation reln, ForkNumber forknum,
- BlockNumber blocknum, char *buffer);
+ BlockNumber blocknum, char *buffer, bool skipInit);
void (*smgr_write) (SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum, char *buffer, bool skipFsync);
void (*smgr_writeback) (SMgrRelation reln, ForkNumber forknum,
@@ -506,9 +506,9 @@ smgrprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum)
*/
void
smgrread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
- char *buffer)
+ char *buffer, bool skipInit)
{
- smgrsw[reln->smgr_which].smgr_read(reln, forknum, blocknum, buffer);
+ smgrsw[reln->smgr_which].smgr_read(reln, forknum, blocknum, buffer, skipInit);
}
/*
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0416549 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 64776e3..4996d88 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1191,6 +1191,110 @@ 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;
+
+ /*
+ * 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 df025a5..dd0b1ff 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1092,6 +1092,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;
@@ -3303,6 +3307,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 53d9ddf..f263b83 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -1156,6 +1156,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/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index b7eee3d..afe22b2 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -18,6 +18,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_statistic_d.h"
#include "funcapi.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_coerce.h"
@@ -30,6 +31,13 @@
#include "utils/syscache.h"
#include "utils/typcache.h"
+/*
+ * TODO: find less ugly way to declare core function returning pg_statistics.
+ * OID of pg_gtt_statistic_for_relation. This function should be handled in special way because it returns set of pg_statistics
+ * which contains attributes of anyarray type. Type of attributes can not be deduced from input parameters and
+ * it prevents using tuple descriptor in this case.
+ */
+#define GttStatisticFunctionId 3434
static void shutdown_MultiFuncCall(Datum arg);
static TypeFuncClass internal_get_result_type(Oid funcid,
@@ -341,7 +349,8 @@ internal_get_result_type(Oid funcid,
if (resolve_polymorphic_tupdesc(tupdesc,
&procform->proargtypes,
- call_expr))
+ call_expr) ||
+ funcid == GttStatisticFunctionId)
{
if (tupdesc->tdtypeid == RECORDOID &&
tupdesc->tdtypmod < 0)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ec3e2c6..4c15822 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15635,8 +15635,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 ad733d1..be38d17 100644
--- a/src/common/relpath.c
+++ b/src/common/relpath.c
@@ -169,7 +169,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 a12fc1f..89c3645 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/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2228256..6757491 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5503,7 +5503,14 @@
proname => 'pg_stat_get_xact_function_self_time', provolatile => 'v',
proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
prosrc => 'pg_stat_get_xact_function_self_time' },
-
+{ oid => '3434',
+ descr => 'show local statistics for global temp table',
+ proname => 'pg_gtt_statistic_for_relation', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '100', proargtypes => 'oid',
+ proallargtypes => '{oid,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,o,o,o,o,o,o,o,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,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,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_gtt_statistic_for_relation' },
{ oid => '3788',
descr => 'statistics: timestamp of the current statistics snapshot',
proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/storage/backendid.h b/src/include/storage/backendid.h
index 0c776a3..124fc3c 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,12 @@ extern PGDLLIMPORT BackendId ParallelMasterBackendId;
#define BackendIdForTempRelations() \
(ParallelMasterBackendId == InvalidBackendId ? MyBackendId : ParallelMasterBackendId)
+
+#define BackendIdForSessionRelations() \
+ (BackendIdForTempRelations() + SessionRelFirstBackendId)
+
+#define IsSessionRelationBackendId(id) ((id) >= SessionRelFirstBackendId)
+
+#define GetRelationBackendId(id) ((id) & ~SessionRelFirstBackendId)
+
#endif /* BACKENDID_H */
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 73c7e9b..467134c 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -231,6 +231,7 @@ extern void TestForOldSnapshot_impl(Snapshot snapshot, Relation relation);
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
+extern void InitGTTIndexes(Relation rel);
/* inline functions */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..7ecef10 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/md.h b/src/include/storage/md.h
index ec7630c..5683883 100644
--- a/src/include/storage/md.h
+++ b/src/include/storage/md.h
@@ -31,7 +31,7 @@ extern void mdextend(SMgrRelation reln, ForkNumber forknum,
extern void mdprefetch(SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum);
extern void mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
- char *buffer);
+ char *buffer, bool skipInit);
extern void mdwrite(SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum, char *buffer, bool skipFsync);
extern void mdwriteback(SMgrRelation reln, ForkNumber forknum,
diff --git a/src/include/storage/relfilenode.h b/src/include/storage/relfilenode.h
index 4de9fc1..c45040b 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/storage/smgr.h b/src/include/storage/smgr.h
index 2438221..a4a2da2 100644
--- a/src/include/storage/smgr.h
+++ b/src/include/storage/smgr.h
@@ -95,7 +95,7 @@ extern void smgrextend(SMgrRelation reln, ForkNumber forknum,
extern void smgrprefetch(SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum);
extern void smgrread(SMgrRelation reln, ForkNumber forknum,
- BlockNumber blocknum, char *buffer);
+ BlockNumber blocknum, char *buffer, bool skipInit);
extern void smgrwrite(SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum, char *buffer, bool skipFsync);
extern void smgrwriteback(SMgrRelation reln, ForkNumber forknum,
diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h
index f4aa316..365b02a 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 44ed04d..ae56427 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
@@ -332,6 +333,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
{
@@ -340,6 +353,7 @@ typedef enum ViewOptCheckOption
VIEW_OPTION_CHECK_OPTION_CASCADED
} ViewOptCheckOption;
+
/*
* ViewOptions
* Contents of rd_options for views
@@ -535,7 +549,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 f27b73d..eaf21d9 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/rules.out b/src/test/regress/expected/rules.out
index 2ab2115..7538601 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,40 @@ 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_statistic| SELECT s.starelid,
+ s.staattnum,
+ s.stainherit,
+ s.stanullfrac,
+ s.stawidth,
+ s.stadistinct,
+ s.stakind1,
+ s.stakind2,
+ s.stakind3,
+ s.stakind4,
+ s.stakind5,
+ s.staop1,
+ s.staop2,
+ s.staop3,
+ s.staop4,
+ s.staop5,
+ s.stacoll1,
+ s.stacoll2,
+ s.stacoll3,
+ s.stacoll4,
+ s.stacoll5,
+ s.stanumbers1,
+ s.stanumbers2,
+ s.stanumbers3,
+ s.stanumbers4,
+ s.stanumbers5,
+ s.stavalues1,
+ s.stavalues2,
+ s.stavalues3,
+ s.stavalues4,
+ s.stavalues5
+ FROM pg_class c,
+ LATERAL pg_gtt_statistic_for_relation(c.oid) 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 = 's'::"char");
pg_hba_file_rules| SELECT a.line_number,
a.type,
a.database,
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 d2b17dd..71c8ca4 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 acba391..71abe08 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;
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: Global temporary tables
@ 2020-02-12 17:28 Phil Florent <philflorent@hotmail.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
0 siblings, 0 replies; 525+ messages in thread
From: Phil Florent @ 2020-02-12 17:28 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Philippe BEAUDOIN <phb07@apra.asso.fr>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; +Cc: pgsql-hackers@lists.postgresql.org <pgsql-hackers@lists.postgresql.org>; Konstantin Knizhnik <knizhnik@garret.ru>
Hi,
I am very interested in this feature that will conform to the SQL standard and I read that :
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.
There is a confusion. Sadly it does not work like that at all with Oracle. Their implementation is buggy in my opinion.
Here is a very simple test case to prove it with the latest version (january 2020) :
Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0<http://19.0.0.0>.0 - Production
Version 19.6.0.0<http://19.6.0.0>.0
-- session 1
create global temporary table gtt(x integer);
Table created.
-- session 2
insert into gtt SELECT level FROM dual CONNECT BY LEVEL <= 100000;
100000 rows created.
-- session 1
create index igtt on gtt(x);
Index created.
-- session 2
select * from gtt where x = 9;
no rows selected
select /*+ FULL(gtt) */ * from gtt where x = 9;
X
----------
9
What happened ? The optimizer (planner) knows the new index igtt can be efficient via dynamic sampling. Hence, igtt is used at execution time...but it is NOT populated. By default I obtained no line. If I force a full scan of the table with a hint /*+ FULL */ you can see that I obtain my line 9. Different results with different exec plans it's a WRONG RESULT bug, the worst kind of bugs.
Please don't consider Oracle as a reference for your implementation. I am 100% sure you can implement and document that better than Oracle. E.g index is populated and considered only for transactions that started after the index creation or something like that. It would be far better than this misleading behaviour.
Regards,
Phil
Télécharger Outlook pour Android<https://aka.ms/ghei36;
________________________________
From: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
Sent: Monday, February 10, 2020 5:48:29 PM
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Philippe BEAUDOIN <phb07@apra.asso.fr>
Cc: pgsql-hackers@lists.postgresql.org <pgsql-hackers@lists.postgresql.org>; Konstantin Knizhnik <knizhnik@garret.ru>
Subject: Re: Global temporary tables
Sorry, small typo in the last patch.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-14 09:19 Pavel Stehule <pavel.stehule@gmail.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
1 sibling, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2020-02-14 09:19 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
čt 30. 1. 2020 v 15:21 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:
>
>
> čt 30. 1. 2020 v 15:17 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
> napsal:
>
>>
>>
>> > 2020年1月29日 下午9:48,Robert Haas <robertmhaas@gmail.com> 写道:
>> >
>> > On Tue, Jan 28, 2020 at 12:12 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
>> wrote:
>> >>> Opinion by Pavel
>> >>> + rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the
>> name of field "rd_islocaltemp" is not probably best
>> >>> I renamed rd_islocaltemp
>> >>
>> >> I don't see any change?
>> >>
>> >> Rename rd_islocaltemp to rd_istemp in
>> global_temporary_table_v8-pg13.patch
>> >
>> > In view of commit 6919b7e3294702adc39effd16634b2715d04f012, I think
>> > that this has approximately a 0% chance of being acceptable. If you're
>> > setting a field in a way that is inconsistent with the current use of
>> > the field, you're probably doing it wrong, because the field has an
>> > existing purpose to which new code must conform. And if you're not
>> > doing that, then you don't need to rename it.
>> Thank you for pointing it out.
>> I've rolled back the rename.
>> But I still need rd_localtemp to be true, The reason is that
>> 1 GTT The GTT needs to support DML in read-only transactions ,like local
>> temp table.
>> 2 GTT does not need to hold the lock before modifying the index buffer
>> ,also like local temp table.
>>
>> Please give me feedback.
>>
>
> maybe some like
>
> rel->rd_globaltemp = true;
>
> and somewhere else
>
> if (rel->rd_localtemp || rel->rd_globaltemp)
> {
> ...
> }
>
>
I tested this patch again and I am very well satisfied with behave.
what doesn't work still - TRUNCATE statement
postgres=# insert into foo select generate_series(1,10000);
INSERT 0 10000
postgres=# \dt+ foo
List of relations
┌────────┬──────┬───────┬───────┬─────────────┬────────┬─────────────┐
│ Schema │ Name │ Type │ Owner │ Persistence │ Size │ Description │
╞════════╪══════╪═══════╪═══════╪═════════════╪════════╪═════════════╡
│ public │ foo │ table │ pavel │ session │ 384 kB │ │
└────────┴──────┴───────┴───────┴─────────────┴────────┴─────────────┘
(1 row)
postgres=# truncate foo;
TRUNCATE TABLE
postgres=# \dt+ foo
List of relations
┌────────┬──────┬───────┬───────┬─────────────┬───────┬─────────────┐
│ Schema │ Name │ Type │ Owner │ Persistence │ Size │ Description │
╞════════╪══════╪═══════╪═══════╪═════════════╪═══════╪═════════════╡
│ public │ foo │ table │ pavel │ session │ 16 kB │ │
└────────┴──────┴───────┴───────┴─────────────┴───────┴─────────────┘
(1 row)
I expect zero size after truncate.
Regards
Pavel
>>
>> Wenjing
>>
>>
>>
>>
>> >
>> > --
>> > Robert Haas
>> > EnterpriseDB: http://www.enterprisedb.com
>> > The Enterprise PostgreSQL Company
>>
>>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-15 09:56 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-02-15 09:56 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 2020年2月14日 下午5:19,Pavel Stehule <pavel.stehule@gmail.com> 写道:
>
>
>
> čt 30. 1. 2020 v 15:21 odesílatel Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> napsal:
>
>
> čt 30. 1. 2020 v 15:17 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
>
>
> > 2020年1月29日 下午9:48,Robert Haas <robertmhaas@gmail.com <mailto:robertmhaas@gmail.com>> 写道:
> >
> > On Tue, Jan 28, 2020 at 12:12 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:
> >>> Opinion by Pavel
> >>> + rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of field "rd_islocaltemp" is not probably best
> >>> I renamed rd_islocaltemp
> >>
> >> I don't see any change?
> >>
> >> Rename rd_islocaltemp to rd_istemp in global_temporary_table_v8-pg13.patch
> >
> > In view of commit 6919b7e3294702adc39effd16634b2715d04f012, I think
> > that this has approximately a 0% chance of being acceptable. If you're
> > setting a field in a way that is inconsistent with the current use of
> > the field, you're probably doing it wrong, because the field has an
> > existing purpose to which new code must conform. And if you're not
> > doing that, then you don't need to rename it.
> Thank you for pointing it out.
> I've rolled back the rename.
> But I still need rd_localtemp to be true, The reason is that
> 1 GTT The GTT needs to support DML in read-only transactions ,like local temp table.
> 2 GTT does not need to hold the lock before modifying the index buffer ,also like local temp table.
>
> Please give me feedback.
>
> maybe some like
>
> rel->rd_globaltemp = true;
>
> and somewhere else
>
> if (rel->rd_localtemp || rel->rd_globaltemp)
> {
> ...
> }
>
>
> I tested this patch again and I am very well satisfied with behave.
>
> what doesn't work still - TRUNCATE statement
>
> postgres=# insert into foo select generate_series(1,10000);
> INSERT 0 10000
> postgres=# \dt+ foo
> List of relations
> ┌────────┬──────┬───────┬───────┬─────────────┬────────┬─────────────┐
> │ Schema │ Name │ Type │ Owner │ Persistence │ Size │ Description │
> ╞════════╪══════╪═══════╪═══════╪═════════════╪════════╪═════════════╡
> │ public │ foo │ table │ pavel │ session │ 384 kB │ │
> └────────┴──────┴───────┴───────┴─────────────┴────────┴─────────────┘
> (1 row)
>
> postgres=# truncate foo;
> TRUNCATE TABLE
> postgres=# \dt+ foo
> List of relations
> ┌────────┬──────┬───────┬───────┬─────────────┬───────┬─────────────┐
> │ Schema │ Name │ Type │ Owner │ Persistence │ Size │ Description │
> ╞════════╪══════╪═══════╪═══════╪═════════════╪═══════╪═════════════╡
> │ public │ foo │ table │ pavel │ session │ 16 kB │ │
> └────────┴──────┴───────┴───────┴─────────────┴───────┴─────────────┘
> (1 row)
>
> I expect zero size after truncate.
Thanks for review.
I can explain, I don't think it's a bug.
The current implementation of the truncated GTT retains two blocks of FSM pages.
The same is true for truncating regular tables in subtransactions.
This is an implementation that truncates the table without changing the relfilenode of the table.
Wenjing
>
> Regards
>
> Pavel
>
>
>
> Wenjing
>
>
>
>
> >
> > --
> > Robert Haas
> > EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/;
> > The Enterprise PostgreSQL Company
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-15 10:06 Pavel Stehule <pavel.stehule@gmail.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 2 replies; 525+ messages in thread
From: Pavel Stehule @ 2020-02-15 10:06 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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.
Regards
Pavel Stehule
>
> Wenjing
>
>
> Regards
>
> Pavel
>
>
>>>
>>> Wenjing
>>>
>>>
>>>
>>>
>>> >
>>> > --
>>> > Robert Haas
>>> > EnterpriseDB: http://www.enterprisedb.com
>>> > The Enterprise PostgreSQL Company
>>>
>>>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-16 15:07 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
1 sibling, 0 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-02-16 15:07 UTC (permalink / raw)
To: Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 2020年2月9日 下午8:05,Konstantin Knizhnik <k.knizhnik@postgrespro.ru> 写道:
>
>
>
> On 07.02.2020 21:37, Pavel Stehule wrote:
>>
>> What when session 2 has active transaction? Then to be correct, you should to wait with index creation to end of transaction.
>>
>>
>> Session1:
>> postgres=# create unique index on gtt(x);
>> CREATE INDEX
>>
>> Sessin2:
>> postgres=# explain select * from gtt where x=1;
>> ERROR: could not create unique index "gtt_x_idx"
>> DETAIL: Key (x)=(1) is duplicated.
>>
>> This is little bit unexpected behave (probably nobody expect so any SELECT fail with error "could not create index" - I understand exactly to reason and context, but this side effect is something what I afraid.
>>
> The more I thinking creation of indexes for GTT on-demand, the more contractions I see.
> So looks like there are only two safe alternatives:
> 1. Allow DDL for GTT (including index creation) only if there are no other sessions using this GTT ("using" means that no data was inserted in GTT by this session). Things can be even more complicated if we take in account inter-table dependencies (like foreign key constraint).
> 2. Create indexes for GTT locally.
>
> 2) seems to be very contradictory (global table metadata, but private indexes) and hard to implement because in this case we have to maintain some private copy of index catalog to keep information about private indexes.
>
> 1) is currently implemented by Wenjing. Frankly speaking I still find such limitation too restrictive and inconvenient for users. From my point of view Oracle developers have implemented better compromise. But if I am the only person voting for such solution, then let's stop this discussion.
> But in any case I think that calling ambuild to construct index for empty table is better solution than implementation of all indexes (and still not solving the problem with custom indexes).
I made some improvements
1 Support for all indexes on GTT (using index_build build empty index).
2 Remove some ugly code in md.c bufmgr.c
Please give me feedback.
Wenjing
Attachments:
[application/octet-stream] global_temporary_table_v11-pg13.patch (154.5K, ../../1A44D7F6-FBEC-4DDB-997C-FF05546F4B81@alibaba-inc.com/3-global_temporary_table_v11-pg13.patch)
download | inline diff:
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;
+
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-16 15:16 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
1 sibling, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-02-16 15:16 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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?
>
> Regards
>
> Pavel Stehule
>
>
> Wenjing
>
>>
>> Regards
>>
>> Pavel
>>
>>
>>
>> Wenjing
>>
>>
>>
>>
>> >
>> > --
>> > Robert Haas
>> > EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/;
>> > The Enterprise PostgreSQL Company
>>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-16 15:22 Pavel Stehule <pavel.stehule@gmail.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 2 replies; 525+ messages in thread
From: Pavel Stehule @ 2020-02-16 15:22 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
>>>>
>>>>
>>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-21 07:45 Prabhat Sahu <prabhat.sahu@enterprisedb.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
1 sibling, 0 replies; 525+ messages in thread
From: Prabhat Sahu @ 2020-02-21 07:45 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-21 15:40 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
1 sibling, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-02-21 15:40 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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:
[application/octet-stream] global_temporary_table_v12-pg13.patch (154.2K, ../../82851B35-E2FB-4A5D-AF35-A2B5023DB9F2@alibaba-inc.com/3-global_temporary_table_v12-pg13.patch)
download | inline diff:
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;
+
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-24 09:44 Prabhat Sahu <prabhat.sahu@enterprisedb.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 2 replies; 525+ messages in thread
From: Prabhat Sahu @ 2020-02-24 09:44 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On Fri, Feb 21, 2020 at 9:10 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:
> Hi,
> I have started testing the "Global temporary table" feature,
> That's great, I see hope.
> from "gtt_v11-pg13.patch". Below is my findings:
>
> -- session 1:
> postgres=# create global temporary table gtt1(a int);
> CREATE TABLE
>
> -- seeeion 2:
> postgres=# truncate gtt1 ;
> ERROR: could not open file "base/13585/t3_16384": No such file or
> directory
>
> is it expected?
>
> Oh ,this is a bug, I fixed it.
>
Thanks for the patch.
I have verified the same, Now the issue is resolved with v12 patch.
Kindly confirm the below scenario:
postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE
postgres=# create global temporary table gtt2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp table
postgres=# create table tab2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp table
Thanks,
Prabhat Sahu
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-24 13:34 Prabhat Sahu <prabhat.sahu@enterprisedb.com>
parent: Prabhat Sahu <prabhat.sahu@enterprisedb.com>
1 sibling, 2 replies; 525+ messages in thread
From: Prabhat Sahu @ 2020-02-24 13:34 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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)
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-24 13:41 Pavel Stehule <pavel.stehule@gmail.com>
parent: Prabhat Sahu <prabhat.sahu@enterprisedb.com>
1 sibling, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2020-02-24 13:41 UTC (permalink / raw)
To: Prabhat Sahu <prabhat.sahu@enterprisedb.com>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
>
> 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
>
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-24 13:57 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Prabhat Sahu <prabhat.sahu@enterprisedb.com>
1 sibling, 0 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-02-24 13:57 UTC (permalink / raw)
To: Prabhat Sahu <prabhat.sahu@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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.
>
>
> 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
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-25 08:53 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 0 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-02-25 08:53 UTC (permalink / raw)
To: Prabhat Sahu <prabhat.sahu@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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
>
>
>
>
> 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:
[application/octet-stream] global_temporary_table_v13-pg13.patch (155.1K, ../../FACE0846-991E-402D-9C76-BEB51D7B9830@alibaba-inc.com/3-global_temporary_table_v13-pg13.patch)
download | inline diff:
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;
+
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-25 08:55 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Prabhat Sahu <prabhat.sahu@enterprisedb.com>
1 sibling, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-02-25 08:55 UTC (permalink / raw)
To: Prabhat Sahu <prabhat.sahu@enterprisedb.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 2020年2月24日 下午5:44,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:
>
> On Fri, Feb 21, 2020 at 9:10 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:
> Hi,
> I have started testing the "Global temporary table" feature,
> That's great, I see hope.
> from "gtt_v11-pg13.patch". Below is my findings:
>
> -- session 1:
> postgres=# create global temporary table gtt1(a int);
> CREATE TABLE
>
> -- seeeion 2:
> postgres=# truncate gtt1 ;
> ERROR: could not open file "base/13585/t3_16384": No such file or directory
>
> is it expected?
>
> Oh ,this is a bug, I fixed it.
> Thanks for the patch.
> I have verified the same, Now the issue is resolved with v12 patch.
>
> Kindly confirm the below scenario:
>
> postgres=# create global temporary table gtt1 (c1 int unique);
> CREATE TABLE
>
> postgres=# create global temporary table gtt2 (c1 int references gtt1(c1) );
> ERROR: referenced relation "gtt1" is not a global temp table
>
> postgres=# create table tab2 (c1 int references gtt1(c1) );
> ERROR: referenced relation "gtt1" is not a global temp table
>
> Thanks,
> Prabhat Sahu
GTT supports foreign key constraints in global_temporary_table_v13-pg13.patch
Wenjing
Attachments:
[application/octet-stream] global_temporary_table_v13-pg13.patch (155.1K, ../../AE2D2EE3-B9E6-4ACA-AC91-304347F224CE@alibaba-inc.com/3-global_temporary_table_v13-pg13.patch)
download | inline diff:
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;
+
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-25 13:36 Prabhat Sahu <prabhat.sahu@enterprisedb.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 3 replies; 525+ messages in thread
From: Prabhat Sahu @ 2020-02-25 13:36 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
Hi All,
Please check the below findings on GTT.
*-- Scenario 1:*
Under "information_schema", We are not allowed to create "temporary table",
whereas we can CREATE/DROP "Global Temporary Table", is it expected ?
postgres=# create temporary table information_schema.temp1(c1 int);
ERROR: cannot create temporary relation in non-temporary schema
LINE 1: create temporary table information_schema.temp1(c1 int);
^
postgres=# create global temporary table information_schema.temp1(c1 int);
CREATE TABLE
postgres=# drop table information_schema.temp1 ;
DROP TABLE
*-- Scenario 2:*
Here I am getting the same error message in both the below cases.
We may add a "global" keyword with GTT related error message.
postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE
postgres=# create temporary table tmp1 (c1 int unique);
CREATE TABLE
postgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
ERROR: constraints on temporary tables may reference only temporary tables
postgres=# create global temporary table gtt2 (c1 int references tmp1(c1) );
ERROR: constraints on temporary tables may reference only temporary tables
Thanks,
Prabhat Sahu
On Tue, Feb 25, 2020 at 2:25 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:
>
>
> 2020年2月24日 下午5:44,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:
>
> On Fri, Feb 21, 2020 at 9:10 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
> wrote:
>
>> Hi,
>> I have started testing the "Global temporary table" feature,
>> That's great, I see hope.
>> from "gtt_v11-pg13.patch". Below is my findings:
>>
>> -- session 1:
>> postgres=# create global temporary table gtt1(a int);
>> CREATE TABLE
>>
>> -- seeeion 2:
>> postgres=# truncate gtt1 ;
>> ERROR: could not open file "base/13585/t3_16384": No such file or
>> directory
>>
>> is it expected?
>>
>> Oh ,this is a bug, I fixed it.
>>
> Thanks for the patch.
> I have verified the same, Now the issue is resolved with v12 patch.
>
> Kindly confirm the below scenario:
>
> postgres=# create global temporary table gtt1 (c1 int unique);
> CREATE TABLE
>
> postgres=# create global temporary table gtt2 (c1 int references gtt1(c1)
> );
> ERROR: referenced relation "gtt1" is not a global temp table
>
> postgres=# create table tab2 (c1 int references gtt1(c1) );
> ERROR: referenced relation "gtt1" is not a global temp table
>
> Thanks,
> Prabhat Sahu
>
>
> GTT supports foreign key constraints
> in global_temporary_table_v13-pg13.patch
>
>
> Wenjing
>
>
>
>
--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-25 13:49 Pavel Stehule <pavel.stehule@gmail.com>
parent: Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2 siblings, 1 reply; 525+ messages in thread
From: Pavel Stehule @ 2020-02-25 13:49 UTC (permalink / raw)
To: Prabhat Sahu <prabhat.sahu@enterprisedb.com>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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
> postgres=# create temporary table information_schema.temp1(c1 int);
> ERROR: cannot create temporary relation in non-temporary schema
> LINE 1: create temporary table information_schema.temp1(c1 int);
> ^
>
> postgres=# create global temporary table information_schema.temp1(c1 int);
> CREATE TABLE
>
> postgres=# drop table information_schema.temp1 ;
> DROP TABLE
>
> *-- Scenario 2:*
> Here I am getting the same error message in both the below cases.
> We may add a "global" keyword with GTT related error message.
>
> postgres=# create global temporary table gtt1 (c1 int unique);
> CREATE TABLE
> postgres=# create temporary table tmp1 (c1 int unique);
> CREATE TABLE
>
> postgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
> ERROR: constraints on temporary tables may reference only temporary tables
>
> postgres=# create global temporary table gtt2 (c1 int references tmp1(c1)
> );
> ERROR: constraints on temporary tables may reference only temporary tables
>
> Thanks,
> Prabhat Sahu
>
> On Tue, Feb 25, 2020 at 2:25 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
> wrote:
>
>>
>>
>> 2020年2月24日 下午5:44,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:
>>
>> On Fri, Feb 21, 2020 at 9:10 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
>> wrote:
>>
>>> Hi,
>>> I have started testing the "Global temporary table" feature,
>>> That's great, I see hope.
>>> from "gtt_v11-pg13.patch". Below is my findings:
>>>
>>> -- session 1:
>>> postgres=# create global temporary table gtt1(a int);
>>> CREATE TABLE
>>>
>>> -- seeeion 2:
>>> postgres=# truncate gtt1 ;
>>> ERROR: could not open file "base/13585/t3_16384": No such file or
>>> directory
>>>
>>> is it expected?
>>>
>>> Oh ,this is a bug, I fixed it.
>>>
>> Thanks for the patch.
>> I have verified the same, Now the issue is resolved with v12 patch.
>>
>> Kindly confirm the below scenario:
>>
>> postgres=# create global temporary table gtt1 (c1 int unique);
>> CREATE TABLE
>>
>> postgres=# create global temporary table gtt2 (c1 int references gtt1(c1)
>> );
>> ERROR: referenced relation "gtt1" is not a global temp table
>>
>> postgres=# create table tab2 (c1 int references gtt1(c1) );
>> ERROR: referenced relation "gtt1" is not a global temp table
>>
>> Thanks,
>> Prabhat Sahu
>>
>>
>> GTT supports foreign key constraints
>> in global_temporary_table_v13-pg13.patch
>>
>>
>> Wenjing
>>
>>
>>
>>
>
> --
>
> With Regards,
> Prabhat Kumar Sahu
> EnterpriseDB: http://www.enterprisedb.com
>
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-25 13:56 tushar <tushar.ahuja@enterprisedb.com>
parent: Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2 siblings, 2 replies; 525+ messages in thread
From: tushar @ 2020-02-25 13:56 UTC (permalink / raw)
To: Prabhat Sahu <prabhat.sahu@enterprisedb.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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 TABLE
>
> postgres=# drop table information_schema.temp1 ;
> DROP TABLE
>
> _-- Scenario 2:_
> Here I am getting the same error message in both the below cases.
> We may add a "global" keyword with GTT related error message.
>
> postgres=# create global temporary table gtt1 (c1 int unique);
> CREATE TABLE
> postgres=# create temporary table tmp1 (c1 int unique);
> CREATE TABLE
>
> postgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
> ERROR: constraints on temporary tables may reference only temporary
> tables
>
> postgres=# create global temporary table gtt2 (c1 int references
> tmp1(c1) );
> ERROR: constraints on temporary tables may reference only temporary
> tables
>
> Thanks,
> Prabhat Sahu
>
> On Tue, Feb 25, 2020 at 2:25 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com
> <mailto:wenjing.zwj@alibaba-inc.com>> wrote:
>
>
>
>> 2020年2月24日 下午5:44,Prabhat Sahu <prabhat.sahu@enterprisedb.com
>> <mailto:prabhat.sahu@enterprisedb.com>> 写道:
>>
>> On Fri, Feb 21, 2020 at 9:10 PM 曾文旌(义从)
>> <wenjing.zwj@alibaba-inc.com
>> <mailto:wenjing.zwj@alibaba-inc.com>> wrote:
>>
>> Hi,
>> I have started testing the "Global temporary table" feature,
>> That's great, I see hope.
>> from "gtt_v11-pg13.patch". Below is my findings:
>>
>> -- session 1:
>> postgres=# create global temporary table gtt1(a int);
>> CREATE TABLE
>>
>> -- seeeion 2:
>> postgres=# truncate gtt1 ;
>> ERROR: could not open file "base/13585/t3_16384": No such
>> file or directory
>>
>> is it expected?
>>
>> Oh ,this is a bug, I fixed it.
>>
>> Thanks for the patch.
>> I have verified the same, Now the issue is resolved with v12 patch.
>>
>> Kindly confirm the below scenario:
>>
>> postgres=# create global temporary table gtt1 (c1 int unique);
>> CREATE TABLE
>>
>> postgres=# create global temporary table gtt2 (c1 int references
>> gtt1(c1) );
>> ERROR: referenced relation "gtt1" is not a global temp table
>>
>> postgres=# create table tab2 (c1 int references gtt1(c1) );
>> ERROR: referenced relation "gtt1" is not a global temp table
>>
>> Thanks,
>> Prabhat Sahu
>
> GTT supports foreign key constraints
> in global_temporary_table_v13-pg13.patch
>
>
> Wenjing
>
>
>
>
>
> --
>
> With Regards,
> Prabhat Kumar Sahu
> EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/;
>
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-25 15:31 tushar <tushar.ahuja@enterprisedb.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: tushar @ 2020-02-25 15:31 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; Prabhat Sahu <prabhat.sahu@enterprisedb.com>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
Hi,
I have created two global temporary tables like this -
Case 1-
postgres=# create global temp table foo(n int) *with
(on_c*ommit_delete_rows='true');
CREATE TABLE
Case 2-
postgres=# create global temp table bar1(n int) *on c*ommit delete rows;
CREATE TABLE
but if i try to do the same having only 'temp' keyword , Case 2 is
working fine but getting this error for case 1 -
postgres=# create temp table foo1(n int) with
(on_commit_delete_rows='true');
ERROR: regular table cannot specifie on_commit_delete_rows
postgres=#
postgres=# create temp table bar1(n int) on commit delete rows;
CREATE TABLE
i think this error message need to be more clear .
regards,
tushar
On 2/25/20 7:19 PM, Pavel Stehule wrote/:
>
>
> út 25. 2. 2020 v 14:36 odesílatel Prabhat Sahu
> <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>>
> napsal:
>
> Hi All,
>
> Please check the below findings on GTT.
> _-- Scenario 1:_
> Under "information_schema", We are not allowed to create
> "temporary table", whereas we can CREATE/DROP "Global Temporary
> Table", is it expected ?
>
>
> It is ok for me. temporary tables should be created only in
> proprietary schema. For GTT there is not risk of collision, so it can
> be created in any schema where are necessary access rights.
>
> Pavel
>
>
> postgres=# create temporary table information_schema.temp1(c1 int);
> ERROR: cannot create temporary relation in non-temporary schema
> LINE 1: create temporary table information_schema.temp1(c1 int);
> ^
>
> postgres=# create global temporary table
> information_schema.temp1(c1 int);
> CREATE TABLE
>
> postgres=# drop table information_schema.temp1 ;
> DROP TABLE
>
> _-- Scenario 2:_
> Here I am getting the same error message in both the below cases.
> We may add a "global" keyword with GTT related error message.
>
> postgres=# create global temporary table gtt1 (c1 int unique);
> CREATE TABLE
> postgres=# create temporary table tmp1 (c1 int unique);
> CREATE TABLE
>
> postgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
> ERROR: constraints on temporary tables may reference only
> temporary tables
>
> postgres=# create global temporary table gtt2 (c1 int references
> tmp1(c1) );
> ERROR: constraints on temporary tables may reference only
> temporary tables
>
> Thanks,
> Prabhat Sahu
>
> On Tue, Feb 25, 2020 at 2:25 PM 曾文旌(义从)
> <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>>
> wrote:
>
>
>
>> 2020年2月24日 下午5:44,Prabhat Sahu
>> <prabhat.sahu@enterprisedb.com
>> <mailto:prabhat.sahu@enterprisedb.com>> 写道:
>>
>> On Fri, Feb 21, 2020 at 9:10 PM 曾文旌(义从)
>> <wenjing.zwj@alibaba-inc.com
>> <mailto:wenjing.zwj@alibaba-inc.com>> wrote:
>>
>> Hi,
>> I have started testing the "Global temporary table" feature,
>> That's great, I see hope.
>> from "gtt_v11-pg13.patch". Below is my findings:
>>
>> -- session 1:
>> postgres=# create global temporary table gtt1(a int);
>> CREATE TABLE
>>
>> -- seeeion 2:
>> postgres=# truncate gtt1 ;
>> ERROR: could not open file "base/13585/t3_16384": No
>> such file or directory
>>
>> is it expected?
>>
>> Oh ,this is a bug, I fixed it.
>>
>> Thanks for the patch.
>> I have verified the same, Now the issue is resolved with v12
>> patch.
>>
>> Kindly confirm the below scenario:
>>
>> postgres=# create global temporary table gtt1 (c1 int unique);
>> CREATE TABLE
>>
>> postgres=# create global temporary table gtt2 (c1 int
>> references gtt1(c1) );
>> ERROR: referenced relation "gtt1" is not a global temp table
>>
>> postgres=# create table tab2 (c1 int references gtt1(c1) );
>> ERROR: referenced relation "gtt1" is not a global temp table
>>
>> Thanks,
>> Prabhat Sahu
>
> GTT supports foreign key constraints
> in global_temporary_table_v13-pg13.patch
>
>
> Wenjing
>
>
>
>
>
> --
>
> With Regards,
> Prabhat Kumar Sahu
> EnterpriseDB: http://www.enterprisedb.com
> <http://www.enterprisedb.com/;
>
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-26 15:52 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: tushar <tushar.ahuja@enterprisedb.com>
1 sibling, 0 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-02-26 15:52 UTC (permalink / raw)
To: tushar <tushar.ahuja@enterprisedb.com>; +Cc: Prabhat Sahu <prabhat.sahu@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
>
> regards,
>
> On 2/25/20 7:06 PM, Prabhat Sahu wrote:
>> Hi All,
>>
>> Please check the below findings on GTT.
>> -- Scenario 1:
>> Under "information_schema", We are not allowed to create "temporary table", whereas we can CREATE/DROP "Global Temporary Table", is it expected ?
>>
>> postgres=# create temporary table information_schema.temp1(c1 int);
>> ERROR: cannot create temporary relation in non-temporary schema
>> LINE 1: create temporary table information_schema.temp1(c1 int);
>> ^
>>
>> postgres=# create global temporary table information_schema.temp1(c1 int);
>> CREATE TABLE
>>
>> postgres=# drop table information_schema.temp1 ;
>> DROP TABLE
>>
>> -- Scenario 2:
>> Here I am getting the same error message in both the below cases.
>> We may add a "global" keyword with GTT related error message.
>>
>> postgres=# create global temporary table gtt1 (c1 int unique);
>> CREATE TABLE
>> postgres=# create temporary table tmp1 (c1 int unique);
>> CREATE TABLE
>>
>> postgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
>> ERROR: constraints on temporary tables may reference only temporary tables
>>
>> postgres=# create global temporary table gtt2 (c1 int references tmp1(c1) );
>> ERROR: constraints on temporary tables may reference only temporary tables
>>
>> Thanks,
>> Prabhat Sahu
>>
>> On Tue, Feb 25, 2020 at 2:25 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:
>>
>>
>>> 2020年2月24日 下午5:44,Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> 写道:
>>>
>>> On Fri, Feb 21, 2020 at 9:10 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:
>>> Hi,
>>> I have started testing the "Global temporary table" feature,
>>> That's great, I see hope.
>>> from "gtt_v11-pg13.patch". Below is my findings:
>>>
>>> -- session 1:
>>> postgres=# create global temporary table gtt1(a int);
>>> CREATE TABLE
>>>
>>> -- seeeion 2:
>>> postgres=# truncate gtt1 ;
>>> ERROR: could not open file "base/13585/t3_16384": No such file or directory
>>>
>>> is it expected?
>>>
>>> Oh ,this is a bug, I fixed it.
>>> Thanks for the patch.
>>> I have verified the same, Now the issue is resolved with v12 patch.
>>>
>>> Kindly confirm the below scenario:
>>>
>>> postgres=# create global temporary table gtt1 (c1 int unique);
>>> CREATE TABLE
>>>
>>> postgres=# create global temporary table gtt2 (c1 int references gtt1(c1) );
>>> ERROR: referenced relation "gtt1" is not a global temp table
>>>
>>> postgres=# create table tab2 (c1 int references gtt1(c1) );
>>> ERROR: referenced relation "gtt1" is not a global temp table
>>>
>>> Thanks,
>>> Prabhat Sahu
>>
>> GTT supports foreign key constraints in global_temporary_table_v13-pg13.patch
>>
>>
>> Wenjing
>>
>>
>>
>>
>>
>> --
>> With Regards,
>> Prabhat Kumar Sahu
>> EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/;
>
> --
> regards,tushar
> EnterpriseDB https://www.enterprisedb.com/ <https://www.enterprisedb.com/;
> The Enterprise PostgreSQL Company
Attachments:
[application/octet-stream] global_temporary_table_v14-pg13.patch (155.8K, ../../BA07E463-5E54-4E7D-90E4-C1500E3F8AD8@alibaba-inc.com/3-global_temporary_table_v14-pg13.patch)
download | inline diff:
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;
+
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-26 15:54 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: tushar <tushar.ahuja@enterprisedb.com>
0 siblings, 0 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-02-26 15:54 UTC (permalink / raw)
To: tushar <tushar.ahuja@enterprisedb.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Prabhat Sahu <prabhat.sahu@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 2020年2月25日 下午11:31,tushar <tushar.ahuja@enterprisedb.com> 写道:
>
> Hi,
>
> I have created two global temporary tables like this -
>
> Case 1-
> postgres=# create global temp table foo(n int) with (on_commit_delete_rows='true');
> CREATE TABLE
>
> Case 2-
> postgres=# create global temp table bar1(n int) on commit delete rows;
> CREATE TABLE
>
>
> but if i try to do the same having only 'temp' keyword , Case 2 is working fine but getting this error for case 1 -
>
> postgres=# create temp table foo1(n int) with (on_commit_delete_rows='true');
> ERROR: regular table cannot specifie on_commit_delete_rows
> postgres=#
>
> postgres=# create temp table bar1(n int) on commit delete rows;
> CREATE TABLE
>
> i think this error message need to be more clear .
Also fixed in global_temporary_table_v14-pg13.patch
Wenjing
>
> regards,
> tushar
>
> On 2/25/20 7:19 PM, Pavel Stehule wrote/:
>>
>>
>> út 25. 2. 2020 v 14:36 odesílatel Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> napsal:
>> Hi All,
>>
>> Please check the below findings on GTT.
>> -- Scenario 1:
>> Under "information_schema", We are not allowed to create "temporary table", whereas we can CREATE/DROP "Global Temporary Table", is it expected ?
>>
>> It is ok for me. temporary tables should be created only in proprietary schema. For GTT there is not risk of collision, so it can be created in any schema where are necessary access rights.
>>
>> Pavel
>>
>>
>> postgres=# create temporary table information_schema.temp1(c1 int);
>> ERROR: cannot create temporary relation in non-temporary schema
>> LINE 1: create temporary table information_schema.temp1(c1 int);
>> ^
>>
>> postgres=# create global temporary table information_schema.temp1(c1 int);
>> CREATE TABLE
>>
>> postgres=# drop table information_schema.temp1 ;
>> DROP TABLE
>>
>> -- Scenario 2:
>> Here I am getting the same error message in both the below cases.
>> We may add a "global" keyword with GTT related error message.
>>
>> postgres=# create global temporary table gtt1 (c1 int unique);
>> CREATE TABLE
>> postgres=# create temporary table tmp1 (c1 int unique);
>> CREATE TABLE
>>
>> postgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
>> ERROR: constraints on temporary tables may reference only temporary tables
>>
>> postgres=# create global temporary table gtt2 (c1 int references tmp1(c1) );
>> ERROR: constraints on temporary tables may reference only temporary tables
>>
>> Thanks,
>> Prabhat Sahu
>>
>> On Tue, Feb 25, 2020 at 2:25 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:
>>
>>
>>> 2020年2月24日 下午5:44,Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> 写道:
>>>
>>> On Fri, Feb 21, 2020 at 9:10 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:
>>> Hi,
>>> I have started testing the "Global temporary table" feature,
>>> That's great, I see hope.
>>> from "gtt_v11-pg13.patch". Below is my findings:
>>>
>>> -- session 1:
>>> postgres=# create global temporary table gtt1(a int);
>>> CREATE TABLE
>>>
>>> -- seeeion 2:
>>> postgres=# truncate gtt1 ;
>>> ERROR: could not open file "base/13585/t3_16384": No such file or directory
>>>
>>> is it expected?
>>>
>>> Oh ,this is a bug, I fixed it.
>>> Thanks for the patch.
>>> I have verified the same, Now the issue is resolved with v12 patch.
>>>
>>> Kindly confirm the below scenario:
>>>
>>> postgres=# create global temporary table gtt1 (c1 int unique);
>>> CREATE TABLE
>>>
>>> postgres=# create global temporary table gtt2 (c1 int references gtt1(c1) );
>>> ERROR: referenced relation "gtt1" is not a global temp table
>>>
>>> postgres=# create table tab2 (c1 int references gtt1(c1) );
>>> ERROR: referenced relation "gtt1" is not a global temp table
>>>
>>> Thanks,
>>> Prabhat Sahu
>>
>> GTT supports foreign key constraints in global_temporary_table_v13-pg13.patch
>>
>>
>> Wenjing
>>
>>
>>
>>
>>
>> --
>> With Regards,
>> Prabhat Kumar Sahu
>> EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/;
>
> --
> regards,tushar
> EnterpriseDB https://www.enterprisedb.com/ <https://www.enterprisedb.com/;
> The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-27 04:12 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: tushar <tushar.ahuja@enterprisedb.com>
1 sibling, 0 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-02-27 04:12 UTC (permalink / raw)
To: tushar <tushar.ahuja@enterprisedb.com>; +Cc: Prabhat Sahu <prabhat.sahu@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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
>
> regards,
>
> On 2/25/20 7:06 PM, Prabhat Sahu wrote:
>> Hi All,
>>
>> Please check the below findings on GTT.
>> -- Scenario 1:
>> Under "information_schema", We are not allowed to create "temporary table", whereas we can CREATE/DROP "Global Temporary Table", is it expected ?
>>
>> postgres=# create temporary table information_schema.temp1(c1 int);
>> ERROR: cannot create temporary relation in non-temporary schema
>> LINE 1: create temporary table information_schema.temp1(c1 int);
>> ^
>>
>> postgres=# create global temporary table information_schema.temp1(c1 int);
>> CREATE TABLE
>>
>> postgres=# drop table information_schema.temp1 ;
>> DROP TABLE
>>
>> -- Scenario 2:
>> Here I am getting the same error message in both the below cases.
>> We may add a "global" keyword with GTT related error message.
>>
>> postgres=# create global temporary table gtt1 (c1 int unique);
>> CREATE TABLE
>> postgres=# create temporary table tmp1 (c1 int unique);
>> CREATE TABLE
>>
>> postgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
>> ERROR: constraints on temporary tables may reference only temporary tables
>>
>> postgres=# create global temporary table gtt2 (c1 int references tmp1(c1) );
>> ERROR: constraints on temporary tables may reference only temporary tables
>>
>> Thanks,
>> Prabhat Sahu
>>
>> On Tue, Feb 25, 2020 at 2:25 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:
>>
>>
>>> 2020年2月24日 下午5:44,Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> 写道:
>>>
>>> On Fri, Feb 21, 2020 at 9:10 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:
>>> Hi,
>>> I have started testing the "Global temporary table" feature,
>>> That's great, I see hope.
>>> from "gtt_v11-pg13.patch". Below is my findings:
>>>
>>> -- session 1:
>>> postgres=# create global temporary table gtt1(a int);
>>> CREATE TABLE
>>>
>>> -- seeeion 2:
>>> postgres=# truncate gtt1 ;
>>> ERROR: could not open file "base/13585/t3_16384": No such file or directory
>>>
>>> is it expected?
>>>
>>> Oh ,this is a bug, I fixed it.
>>> Thanks for the patch.
>>> I have verified the same, Now the issue is resolved with v12 patch.
>>>
>>> Kindly confirm the below scenario:
>>>
>>> postgres=# create global temporary table gtt1 (c1 int unique);
>>> CREATE TABLE
>>>
>>> postgres=# create global temporary table gtt2 (c1 int references gtt1(c1) );
>>> ERROR: referenced relation "gtt1" is not a global temp table
>>>
>>> postgres=# create table tab2 (c1 int references gtt1(c1) );
>>> ERROR: referenced relation "gtt1" is not a global temp table
>>>
>>> Thanks,
>>> Prabhat Sahu
>>
>> GTT supports foreign key constraints in global_temporary_table_v13-pg13.patch
>>
>>
>> Wenjing
>>
>>
>>
>>
>>
>> --
>> With Regards,
>> Prabhat Kumar Sahu
>> EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/;
>
> --
> regards,tushar
> EnterpriseDB https://www.enterprisedb.com/ <https://www.enterprisedb.com/;
> The Enterprise PostgreSQL Company
Attachments:
[application/octet-stream] global_temporary_table_v15-pg13.patch (163.4K, ../../209679C2-4D4A-4BA4-9819-33FCC1B00296@alibaba-inc.com/3-global_temporary_table_v15-pg13.patch)
download | inline diff:
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;
+
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-02-27 04:13 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2 siblings, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-02-27 04:13 UTC (permalink / raw)
To: Prabhat Sahu <prabhat.sahu@enterprisedb.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 2020年2月25日 下午9:36,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:
>
> Hi All,
>
> Please check the below findings on GTT.
> -- Scenario 1:
> Under "information_schema", We are not allowed to create "temporary table", whereas we can CREATE/DROP "Global Temporary Table", is it expected ?
>
> postgres=# create temporary table information_schema.temp1(c1 int);
> ERROR: cannot create temporary relation in non-temporary schema
> LINE 1: create temporary table information_schema.temp1(c1 int);
> ^
>
> postgres=# create global temporary table information_schema.temp1(c1 int);
> CREATE TABLE
>
> postgres=# drop table information_schema.temp1 ;
> DROP TABLE
>
> -- Scenario 2:
> Here I am getting the same error message in both the below cases.
> We may add a "global" keyword with GTT related error message.
>
> postgres=# create global temporary table gtt1 (c1 int unique);
> CREATE TABLE
> postgres=# create temporary table tmp1 (c1 int unique);
> CREATE TABLE
>
> postgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
> ERROR: constraints on temporary tables may reference only temporary tables
>
> postgres=# create global temporary table gtt2 (c1 int references tmp1(c1) );
> ERROR: constraints on temporary tables may reference only temporary tables
Fixed in global_temporary_table_v15-pg13.patch
Wenjing
>
> Thanks,
> Prabhat Sahu
>
> On Tue, Feb 25, 2020 at 2:25 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:
>
>
>> 2020年2月24日 下午5:44,Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> 写道:
>>
>> On Fri, Feb 21, 2020 at 9:10 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:
>> Hi,
>> I have started testing the "Global temporary table" feature,
>> That's great, I see hope.
>> from "gtt_v11-pg13.patch". Below is my findings:
>>
>> -- session 1:
>> postgres=# create global temporary table gtt1(a int);
>> CREATE TABLE
>>
>> -- seeeion 2:
>> postgres=# truncate gtt1 ;
>> ERROR: could not open file "base/13585/t3_16384": No such file or directory
>>
>> is it expected?
>>
>> Oh ,this is a bug, I fixed it.
>> Thanks for the patch.
>> I have verified the same, Now the issue is resolved with v12 patch.
>>
>> Kindly confirm the below scenario:
>>
>> postgres=# create global temporary table gtt1 (c1 int unique);
>> CREATE TABLE
>>
>> postgres=# create global temporary table gtt2 (c1 int references gtt1(c1) );
>> ERROR: referenced relation "gtt1" is not a global temp table
>>
>> postgres=# create table tab2 (c1 int references gtt1(c1) );
>> ERROR: referenced relation "gtt1" is not a global temp table
>>
>> Thanks,
>> Prabhat Sahu
>
> GTT supports foreign key constraints in global_temporary_table_v13-pg13.patch
>
>
> Wenjing
>
>
>
>
>
> --
> With Regards,
> Prabhat Kumar Sahu
> EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/;
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-03-02 14:47 tushar <tushar.ahuja@enterprisedb.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 1 reply; 525+ messages in thread
From: tushar @ 2020-03-02 14:47 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Prabhat Sahu <prabhat.sahu@enterprisedb.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On 2/27/20 9:43 AM, 曾文旌(义从) wrote:
>> _-- Scenario 2:_
>> Here I am getting the same error message in both the below cases.
>> We may add a "global" keyword with GTT related error message.
>>
>> postgres=# create global temporary table gtt1 (c1 int unique);
>> CREATE TABLE
>> postgres=# create temporary table tmp1 (c1 int unique);
>> CREATE TABLE
>>
>> postgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
>> ERROR: constraints on temporary tables may reference only temporary
>> tables
>>
>> postgres=# create global temporary table gtt2 (c1 int references
>> tmp1(c1) );
>> ERROR: constraints on temporary tables may reference only temporary
>> tables
> Fixed in global_temporary_table_v15-pg13.patch
>
>
Thanks Wenjing.
This below scenario is not working i.e even 'on_commit_delete_rows' is
true then after commit - rows are NOT removing
postgres=# create global temp table foo1(n int) with
(on_commit_delete_rows='true');
CREATE TABLE
postgres=#
postgres=# begin;
BEGIN
postgres=*# insert into foo1 values (9);
INSERT 0 1
postgres=*# insert into foo1 values (9);
INSERT 0 1
postgres=*# select * from foo1;
n
---
9
9
(2 rows)
postgres=*# commit;
COMMIT
postgres=# select * from foo1; -- after commit -there should be 0 row
as on_commit_delete_rows is 'true'
n
---
9
9
(2 rows)
postgres=# \d+ foo1
Table "public.foo1"
Column | Type | Collation | Nullable | Default | Storage | Stats
target | Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
n | integer | | | | plain
| |
Access method: heap
Options: on_commit_delete_rows=true
postgres=#
but if user - create table this way then it is working as expected
postgres=# create global temp table foo2(n int) *on commit delete rows;*
CREATE TABLE
postgres=# begin; insert into foo2 values (9); insert into foo2 values
(9); commit; select * from foo2;
BEGIN
INSERT 0 1
INSERT 0 1
COMMIT
n
---
(0 rows)
postgres=#
i guess , problem is something with this syntax - create global temp
table foo1(n int) *with (on_commit_delete_rows='true'); *
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-03-03 08:40 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: tushar <tushar.ahuja@enterprisedb.com>
0 siblings, 3 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-03-03 08:40 UTC (permalink / raw)
To: tushar <tushar.ahuja@enterprisedb.com>; +Cc: Prabhat Sahu <prabhat.sahu@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 2020年3月2日 下午10:47,tushar <tushar.ahuja@enterprisedb.com> 写道:
>
> On 2/27/20 9:43 AM, 曾文旌(义从) wrote:
>>> -- Scenario 2:
>>> Here I am getting the same error message in both the below cases.
>>> We may add a "global" keyword with GTT related error message.
>>>
>>> postgres=# create global temporary table gtt1 (c1 int unique);
>>> CREATE TABLE
>>> postgres=# create temporary table tmp1 (c1 int unique);
>>> CREATE TABLE
>>>
>>> postgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
>>> ERROR: constraints on temporary tables may reference only temporary tables
>>>
>>> postgres=# create global temporary table gtt2 (c1 int references tmp1(c1) );
>>> ERROR: constraints on temporary tables may reference only temporary tables
>> Fixed in global_temporary_table_v15-pg13.patch
>>
>>
> Thanks Wenjing.
>
> This below scenario is not working i.e even 'on_commit_delete_rows' is true then after commit - rows are NOT removing
>
> postgres=# create global temp table foo1(n int) with (on_commit_delete_rows='true');
> CREATE TABLE
> postgres=#
> postgres=# begin;
> BEGIN
> postgres=*# insert into foo1 values (9);
> INSERT 0 1
> postgres=*# insert into foo1 values (9);
> INSERT 0 1
> postgres=*# select * from foo1;
> n
> ---
> 9
> 9
> (2 rows)
>
> postgres=*# commit;
> COMMIT
> postgres=# select * from foo1; -- after commit -there should be 0 row as on_commit_delete_rows is 'true'
> n
> ---
> 9
> 9
> (2 rows)
>
> postgres=# \d+ foo1
> Table "public.foo1"
> Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
> --------+---------+-----------+----------+---------+---------+--------------+-------------
> n | integer | | | | plain | |
> Access method: heap
> Options: on_commit_delete_rows=true
>
> postgres=#
>
> but if user - create table this way then it is working as expected
>
> postgres=# create global temp table foo2(n int) on commit delete rows;
> CREATE TABLE
> postgres=# begin; insert into foo2 values (9); insert into foo2 values (9); commit; select * from foo2;
> BEGIN
> INSERT 0 1
> INSERT 0 1
> COMMIT
> n
> ---
> (0 rows)
>
> postgres=#
>
> i guess , problem is something with this syntax - create global temp table foo1(n int) with (on_commit_delete_rows='true');
>
Thanks for review.
I fixed in global_temporary_table_v16-pg13.patch.
Wenjing
> --
> regards,tushar
> EnterpriseDB https://www.enterprisedb.com/ <https://www.enterprisedb.com/;
> The Enterprise PostgreSQL Company
Attachments:
[application/octet-stream] global_temporary_table_v16-pg13.patch (164.0K, ../../E75612C2-00C9-4997-9D37-762A3D0E8482@alibaba-inc.com/3-global_temporary_table_v16-pg13.patch)
download | inline diff:
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;
+
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-03-04 07:49 Prabhat Sahu <prabhat.sahu@enterprisedb.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2 siblings, 1 reply; 525+ messages in thread
From: Prabhat Sahu @ 2020-03-04 07:49 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: tushar <tushar.ahuja@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-03-04 15:39 tushar <tushar.ahuja@enterprisedb.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2 siblings, 1 reply; 525+ messages in thread
From: tushar @ 2020-03-04 15:39 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Prabhat Sahu <prabhat.sahu@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-03-05 14:19 tushar <tushar.ahuja@enterprisedb.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2 siblings, 2 replies; 525+ messages in thread
From: tushar @ 2020-03-05 14:19 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Prabhat Sahu <prabhat.sahu@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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 ?
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-03-05 14:38 Robert Haas <robertmhaas@gmail.com>
parent: tushar <tushar.ahuja@enterprisedb.com>
1 sibling, 1 reply; 525+ messages in thread
From: Robert Haas @ 2020-03-05 14:38 UTC (permalink / raw)
To: tushar <tushar.ahuja@enterprisedb.com>; +Cc: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; Prabhat Sahu <prabhat.sahu@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-03-06 07:08 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: tushar <tushar.ahuja@enterprisedb.com>
1 sibling, 0 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-03-06 07:08 UTC (permalink / raw)
To: tushar <tushar.ahuja@enterprisedb.com>; +Cc: Prabhat Sahu <prabhat.sahu@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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
> 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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-03-06 07:10 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-03-06 07:10 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; tushar <tushar.ahuja@enterprisedb.com>; +Cc: Prabhat Sahu <prabhat.sahu@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 2020年3月5日 下午10:38,Robert Haas <robertmhaas@gmail.com> 写道:
>
> On Thu, Mar 5, 2020 at 9:19 AM tushar <tushar.ahuja@enterprisedb.com> wrote:
>> WARNING: relfilenode 13589/1663/19063 not exist in gtt shared hash when forget
>> ERROR: out of shared memory
>> HINT: You might need to increase max_active_gtt.
>>
>> also , would be great if we can make this error message user friendly like - "max connection reached" rather than memory error
>
> That would be nice, but the bigger problem is that the WARNING there
> looks totally unacceptable. It's looks like it's complaining of some
> internal issue (i.e. a bug or corruption) and the grammar is poor,
> too.
Yes, WARNING should not exist.
This is a bug in the rollback process and I have fixed it in global_temporary_table_v17-pg13.patch
Wenjing
>
> --
> Robert Haas
> EnterpriseDB: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-03-09 12:24 Prabhat Sahu <prabhat.sahu@enterprisedb.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 1 reply; 525+ messages in thread
From: Prabhat Sahu @ 2020-03-09 12:24 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; tushar <tushar.ahuja@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
Hi All,
Kindly check the below scenario.
*Case 1: *
postgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 int) on commit delete rows;
CREATE TABLE
postgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 int) on commit preserve
rows;
CREATE TABLE
postgres=# vacuum gtt1;
VACUUM
postgres=# vacuum gtt2;
VACUUM
postgres=# vacuum;
VACUUM
postgres=# \q
*Case 2: Exit and reconnect to psql prompt.*
[edb@localhost bin]$ ./psql postgres
psql (13devel)
Type "help" for help.
postgres=# vacuum gtt1;
WARNING: skipping vacuum empty global temp table "gtt1"
VACUUM
postgres=# vacuum gtt2;
WARNING: skipping vacuum empty global temp table "gtt2"
VACUUM
postgres=# vacuum;
WARNING: skipping vacuum empty global temp table "gtt1"
WARNING: skipping vacuum empty global temp table "gtt2"
VACUUM
Although in "Case1" the gtt1/gtt2 are empty, we are not getting "WARNING:
skipping vacuum empty global temp table" for VACUUM in "Case 1".
whereas we are getting the "WARNING" for VACUUM in "Case2".
On Fri, Mar 6, 2020 at 12:41 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:
>
>
> > 2020年3月5日 下午10:38,Robert Haas <robertmhaas@gmail.com> 写道:
> >
> > On Thu, Mar 5, 2020 at 9:19 AM tushar <tushar.ahuja@enterprisedb.com>
> wrote:
> >> WARNING: relfilenode 13589/1663/19063 not exist in gtt shared hash
> when forget
> >> ERROR: out of shared memory
> >> HINT: You might need to increase max_active_gtt.
> >>
> >> also , would be great if we can make this error message user friendly
> like - "max connection reached" rather than memory error
> >
> > That would be nice, but the bigger problem is that the WARNING there
> > looks totally unacceptable. It's looks like it's complaining of some
> > internal issue (i.e. a bug or corruption) and the grammar is poor,
> > too.
>
> Yes, WARNING should not exist.
> This is a bug in the rollback process and I have fixed it in
> global_temporary_table_v17-pg13.patch
>
>
> Wenjing
>
>
> >
> > --
> > Robert Haas
> > EnterpriseDB: http://www.enterprisedb.com
> > The Enterprise PostgreSQL Company
>
>
>
>
--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-03-09 13:34 tushar <tushar.ahuja@enterprisedb.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
1 sibling, 1 reply; 525+ messages in thread
From: tushar @ 2020-03-09 13:34 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Prabhat Sahu <prabhat.sahu@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-03-09 14:37 tushar <tushar.ahuja@enterprisedb.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
1 sibling, 1 reply; 525+ messages in thread
From: tushar @ 2020-03-09 14:37 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Prabhat Sahu <prabhat.sahu@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-03-09 16:31 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: tushar <tushar.ahuja@enterprisedb.com>
0 siblings, 2 replies; 525+ messages in thread
From: 曾文旌(义从) @ 2020-03-09 16:31 UTC (permalink / raw)
To: tushar <tushar.ahuja@enterprisedb.com>; +Cc: Prabhat Sahu <prabhat.sahu@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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
>
> 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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-03-11 07:52 Prabhat Sahu <prabhat.sahu@enterprisedb.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
1 sibling, 2 replies; 525+ messages in thread
From: Prabhat Sahu @ 2020-03-11 07:52 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: tushar <tushar.ahuja@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
On Mon, Mar 9, 2020 at 10:02 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:
>
>
> Fixed in global_temporary_table_v18-pg13.patch.
>
Hi Wenjing,
Thanks for the patch. I have verified the previous issues with
"gtt_v18_pg13.patch" and those are resolved.
Please find below case:
postgres=# create sequence seq;
CREATE SEQUENCE
postgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 int PRIMARY KEY) ON COMMIT
DELETE ROWS;
CREATE TABLE
postgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 int PRIMARY KEY) ON COMMIT
PRESERVE ROWS;
CREATE TABLE
postgres=# alter table gtt1 add c2 int default nextval('seq');
ERROR: cannot reindex global temporary tables
postgres=# alter table gtt2 add c2 int default nextval('seq');
ERROR: cannot reindex global temporary tables
*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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-03-11 20:12 Robert Haas <robertmhaas@gmail.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 1 reply; 525+ messages in thread
From: Robert Haas @ 2020-03-11 20:12 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Prabhat Sahu <prabhat.sahu@enterprisedb.com>; tushar <tushar.ahuja@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; shaocong.xsc@alibaba-inc.com(铁庵) <shaocong.xsc@alibaba-inc.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.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-03-12 10:06 =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 525+ messages in thread
From: 曾文旌(义从) @ 2020-03-12 10:06 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; Prabhat Sahu <prabhat.sahu@enterprisedb.com>; tushar <tushar.ahuja@enterprisedb.com>; +Cc: Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
> 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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-03-12 12:22 Prabhat Sahu <prabhat.sahu@enterprisedb.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
0 siblings, 1 reply; 525+ messages in thread
From: Prabhat Sahu @ 2020-03-12 12:22 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; tushar <tushar.ahuja@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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)
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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-03-13 12:40 tushar <tushar.ahuja@enterprisedb.com>
parent: =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
1 sibling, 2 replies; 525+ messages in thread
From: tushar @ 2020-03-13 12:40 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: Prabhat Sahu <prabhat.sahu@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-03-13 14:16 Prabhat Sahu <prabhat.sahu@enterprisedb.com>
parent: tushar <tushar.ahuja@enterprisedb.com>
1 sibling, 2 replies; 525+ messages in thread
From: Prabhat Sahu @ 2020-03-13 14:16 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: tushar <tushar.ahuja@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.com>
Hi Wenjing,
Please check the below combination of GTT with Primary and Foreign key
relations, with the ERROR message.
*Case1:*postgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 serial PRIMARY
KEY, c2 VARCHAR (50) UNIQUE NOT NULL) ON COMMIT *DELETE* ROWS;
CREATE TABLE
postgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 integer NOT NULL, c2
integer NOT NULL,
PRIMARY KEY (c1, c2),
FOREIGN KEY (c1) REFERENCES gtt1 (c1)) ON COMMIT *PRESERVE* ROWS;
ERROR: unsupported ON COMMIT and foreign key combination
DETAIL: Table "gtt2" references "gtt1", but *they do not have the same ON
COMMIT setting*.
*Case2:*
postgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 serial PRIMARY KEY, c2
VARCHAR (50) UNIQUE NOT NULL) ON COMMIT *PRESERVE* ROWS;
CREATE TABLE
postgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 integer NOT NULL, c2
integer NOT NULL,
PRIMARY KEY (c1, c2),
FOREIGN KEY (c1) REFERENCES gtt1 (c1)) ON COMMIT *DELETE* ROWS;
CREATE TABLE
In "case2" although both the primary table and foreign key GTT *do not have
the same ON COMMIT setting*, still we are able to create the PK-FK
relations with GTT.
So I hope the detail message(DETAIL: Table "gtt2" references "gtt1", but
they do not have the same ON COMMIT setting.) in "Case1" should be more
clear(something like "wrong combination of ON COMMIT setting").
--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-03-16 06:23 Prabhat Sahu <prabhat.sahu@enterprisedb.com>
parent: Prabhat Sahu <prabhat.sahu@enterprisedb.com>
1 sibling, 2 replies; 525+ messages in thread
From: Prabhat Sahu @ 2020-03-16 06:23 UTC (permalink / raw)
To: 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: tushar <tushar.ahuja@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Konstantin Knizhnik <k.knizhnik@postgrespro.ru>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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
^ permalink raw reply [nested|flat] 525+ messages in thread
* Re: [Proposal] Global temporary tables
@ 2020-03-16 07:59 Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
parent: Prabhat Sahu <prabhat.sahu@enterprisedb.com>
1 sibling, 1 reply; 525+ messages in thread
From: Konstantin Knizhnik @ 2020-03-16 07:59 UTC (permalink / raw)
To: Prabhat Sahu <prabhat.sahu@enterprisedb.com>; 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>; +Cc: tushar <tushar.ahuja@enterprisedb.com>; Pavel Stehule <pavel.stehule@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; pgsql-hackers; 蔡松露(子嘉) <zijia@taobao.com>; Cai, Le <le.cai@alibaba-inc.com>; 萧少聪(铁庵) <shaocong.xsc@alibaba-inc.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 MAPPING
>
> postgres=# create table lt1 (c1 integer, c2 varchar(50));
> CREATE TABLE
> postgres=# insert into lt1 values (1,'c21');
> INSERT 0 1
> postgres=# create foreign table ft1 (c1 integer, c2 varchar(50))
> server fdw options (table_name 'lt1');
> CREATE FOREIGN TABLE
> postgres=# select * from ft1;
> c1 | c2
> ----+-----
> 1 | c21
> (1 row)
>
> postgres=# create global temporary table gtt1 (c1 integer, c2
> varchar(50));
> CREATE TABLE
> postgres=# insert into gtt1 values (1,'gtt_c21');
> INSERT 0 1
> postgres=# create foreign table f_gtt1 (c1 integer, c2 varchar(50))
> server fdw options (table_name 'gtt1');
> CREATE FOREIGN TABLE
>
> postgres=# select * from gtt1;
> c1 | c2
> ----+---------
> 1 | gtt_c21
> (1 row)
>
> postgres=# select * from f_gtt1;
> c1 | c2
> ----+----
> (0 rows)
>
> --
>
> With Regards,
> Prabhat Kumar Sahu
> EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/;
>
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
^ permalink raw reply [nested|flat] 525+ messages in thread
215 further messages in this thread omitted from this page (render size limit).
Use the per-message pages, or the t.mbox.gz byte stream, for the complete thread.
end of thread, other threads:[~2026-07-03 23:32 UTC | newest]
Thread overview: 525+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2010-04-24 02:52 global temporary tables Robert Haas <robertmhaas@gmail.com>
2010-04-24 03:11 ` Tom Lane <tgl@sss.pgh.pa.us>
2010-04-24 03:18 ` Robert Haas <robertmhaas@gmail.com>
2010-04-24 03:28 ` Tom Lane <tgl@sss.pgh.pa.us>
2010-04-24 03:46 ` Robert Haas <robertmhaas@gmail.com>
2010-04-24 16:02 ` Greg Sabino Mullane <greg@turnstep.com>
2010-04-24 16:11 ` Tom Lane <tgl@sss.pgh.pa.us>
2010-04-24 17:16 ` Robert Haas <robertmhaas@gmail.com>
2010-04-24 17:31 ` Tom Lane <tgl@sss.pgh.pa.us>
2010-04-24 18:32 ` Robert Haas <robertmhaas@gmail.com>
2010-04-24 18:41 ` Pavel Stehule <pavel.stehule@gmail.com>
2010-04-25 00:47 ` Jim Nasby <decibel@decibel.org>
2010-04-25 01:14 ` Robert Haas <robertmhaas@gmail.com>
2010-04-25 01:37 ` Tom Lane <tgl@sss.pgh.pa.us>
2010-04-25 01:59 ` Robert Haas <robertmhaas@gmail.com>
2010-04-25 02:22 ` Robert Haas <robertmhaas@gmail.com>
2010-04-25 02:28 ` Tom Lane <tgl@sss.pgh.pa.us>
2010-04-25 02:57 ` Robert Haas <robertmhaas@gmail.com>
2010-04-25 03:02 ` Tom Lane <tgl@sss.pgh.pa.us>
2010-04-25 12:46 ` Robert Haas <robertmhaas@gmail.com>
2010-04-25 14:26 ` Jim Nasby <decibel@decibel.org>
2010-04-25 19:02 ` Tom Lane <tgl@sss.pgh.pa.us>
2010-04-25 01:51 ` Jim Nasby <decibel@decibel.org>
2010-04-24 17:38 ` Tom Lane <tgl@sss.pgh.pa.us>
2010-04-24 18:51 ` Robert Haas <robertmhaas@gmail.com>
2010-04-24 19:51 ` Tom Lane <tgl@sss.pgh.pa.us>
2010-04-24 22:53 ` Robert Haas <robertmhaas@gmail.com>
2010-04-26 20:18 ` Alvaro Herrera <alvherre@commandprompt.com>
2010-04-24 06:42 ` Pavel Stehule <pavel.stehule@gmail.com>
2010-04-24 22:29 ` Simon Riggs <simon@2ndQuadrant.com>
2010-04-24 23:01 ` Robert Haas <robertmhaas@gmail.com>
2010-04-25 07:49 ` Simon Riggs <simon@2ndQuadrant.com>
2010-04-25 10:50 ` Robert Haas <robertmhaas@gmail.com>
2010-04-25 12:57 ` Simon Riggs <simon@2ndQuadrant.com>
2010-04-25 15:35 ` Robert Haas <robertmhaas@gmail.com>
2010-04-26 19:30 ` Simon Riggs <simon@2ndQuadrant.com>
2010-04-26 19:40 ` Tom Lane <tgl@sss.pgh.pa.us>
2010-04-26 19:50 ` Simon Riggs <simon@2ndQuadrant.com>
2010-04-26 20:06 ` Tom Lane <tgl@sss.pgh.pa.us>
2010-04-26 20:02 ` Robert Haas <robertmhaas@gmail.com>
2010-04-25 21:29 ` Josh Berkus <josh@agliodbs.com>
2010-04-25 21:51 ` Andrew Dunstan <andrew@dunslane.net>
2010-04-25 21:54 ` Robert Haas <robertmhaas@gmail.com>
2019-07-31 15:05 Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-08-01 03:10 ` Re: Global temporary tables Craig Ringer <craig@2ndquadrant.com>
2019-08-01 08:13 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-08-06 08:31 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-08-08 02:40 ` Re: Global temporary tables Craig Ringer <craig@2ndquadrant.com>
2019-08-08 07:03 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-08-09 05:34 ` Re: Global temporary tables Craig Ringer <craig@2ndquadrant.com>
2019-08-09 14:07 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-08-10 02:12 ` Re: Global temporary tables Craig Ringer <craig@2ndquadrant.com>
2019-08-11 06:52 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-08-11 07:14 ` Re: Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2019-08-12 16:19 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-08-12 16:47 ` Re: Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2019-08-13 05:34 ` Re: Global temporary tables Craig Ringer <craig@2ndquadrant.com>
2019-08-13 08:19 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-08-13 08:27 ` Re: Global temporary tables Craig Ringer <craig@2ndquadrant.com>
2019-08-13 13:50 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-08-16 06:25 ` Re: Global temporary tables Craig Ringer <craig@2ndquadrant.com>
2019-08-16 07:30 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-08-16 08:32 ` Re: Global temporary tables Craig Ringer <craig@2ndquadrant.com>
2019-08-16 08:37 ` Re: Global temporary tables Craig Ringer <craig@2ndquadrant.com>
2019-08-16 11:21 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-08-16 11:41 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-08-16 14:12 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-08-16 17:17 ` Re: Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2019-08-18 07:01 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-08-18 08:28 ` Re: Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2019-08-19 08:51 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-08-19 11:16 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-08-19 11:25 ` Re: Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2019-08-19 11:51 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-08-19 15:53 ` Re: Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2019-08-20 14:51 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-08-20 16:06 ` Re: Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2019-08-20 16:42 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-08-20 17:01 ` Re: Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2019-08-21 08:54 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-09-18 10:04 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-09-20 15:12 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-09-25 20:28 ` Re: Global temporary tables Alvaro Herrera <alvherre@2ndquadrant.com>
2019-09-26 07:05 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-11-08 12:43 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-11-11 14:54 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-11-20 16:32 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-12-01 01:56 ` Re: Global temporary tables Michael Paquier <michael@paquier.xyz>
2019-12-02 09:55 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-12-22 17:04 ` Re: Global temporary tables Philippe BEAUDOIN <phb07@apra.asso.fr>
2020-02-01 11:49 ` Re: Global temporary tables Tomas Vondra <tomas.vondra@2ndquadrant.com>
2020-02-03 20:56 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-02-07 17:31 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-02-10 16:48 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-02-12 17:28 ` Re: Global temporary tables Phil Florent <philflorent@hotmail.com>
2019-09-20 16:43 ` Re: Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2019-09-23 07:57 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-09-23 16:50 ` Re: Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2019-08-13 08:21 ` Re: Global temporary tables Craig Ringer <craig@2ndquadrant.com>
2019-08-13 09:20 ` Re: Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-10-11 12:15 [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2019-10-11 13:50 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-10-12 05:16 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2019-10-15 09:49 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2019-10-17 10:18 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2019-10-25 15:01 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2019-10-25 15:13 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2019-10-28 12:07 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2019-10-28 13:37 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-10-28 16:40 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2019-10-29 08:04 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-11-06 13:24 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2019-11-06 16:08 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-11-07 09:30 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2019-11-07 09:40 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2019-11-07 12:17 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2019-11-07 12:29 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2019-11-07 11:49 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2019-11-07 16:32 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-11-08 07:50 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2019-11-08 12:57 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-10-25 16:22 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-10-25 17:00 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2019-11-01 15:15 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-11-01 15:26 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2019-11-01 16:09 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-11-02 05:30 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2019-11-02 07:19 ` Re: [Proposal] Global temporary tables Julien Rouhaud <rjuju123@gmail.com>
2019-11-02 07:23 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2019-11-02 07:24 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2019-11-02 08:56 ` Re: [Proposal] Global temporary tables Julien Rouhaud <rjuju123@gmail.com>
2019-11-02 15:09 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-11-02 15:30 ` Re: [Proposal] Global temporary tables Julien Rouhaud <rjuju123@gmail.com>
2019-11-02 15:15 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-11-02 16:34 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2019-11-08 15:06 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2019-11-11 15:19 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-01-05 20:06 ` Re: [Proposal] Global temporary tables Tomas Vondra <tomas.vondra@2ndquadrant.com>
2020-01-06 05:04 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-06 11:01 ` Re: [Proposal] Global temporary tables Tomas Vondra <tomas.vondra@2ndquadrant.com>
2020-01-06 12:17 ` Re: [Proposal] Global temporary tables Dean Rasheed <dean.a.rasheed@gmail.com>
2020-01-06 12:52 ` Re: [Proposal] Global temporary tables Tomas Vondra <tomas.vondra@2ndquadrant.com>
2020-01-06 13:50 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-01-08 07:03 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-09 15:07 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-01-09 16:30 ` Re: [Proposal] Global temporary tables Tomas Vondra <tomas.vondra@2ndquadrant.com>
2020-01-10 12:24 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-01-12 01:14 ` Re: [Proposal] Global temporary tables Tomas Vondra <tomas.vondra@2ndquadrant.com>
2020-01-14 14:15 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-09 11:17 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-01-09 16:48 ` Re: [Proposal] Global temporary tables Tomas Vondra <tomas.vondra@2ndquadrant.com>
2020-01-10 08:47 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-01-12 01:51 ` Re: [Proposal] Global temporary tables Tomas Vondra <tomas.vondra@2ndquadrant.com>
2020-01-13 08:08 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-01-13 16:32 ` Re: [Proposal] Global temporary tables Tomas Vondra <tomas.vondra@2ndquadrant.com>
2020-01-13 20:12 ` Re: [Proposal] Global temporary tables Julien Rouhaud <rjuju123@gmail.com>
2020-01-13 21:03 ` Re: [Proposal] Global temporary tables Tomas Vondra <tomas.vondra@2ndquadrant.com>
2020-01-15 13:10 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-16 07:23 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-01-23 20:47 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2020-01-24 08:39 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-01-24 09:09 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-01-24 09:43 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-01-24 12:15 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-01-24 13:17 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-01-24 19:39 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-01-27 09:11 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-01-27 19:44 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-01-29 08:12 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-01-29 14:47 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2020-01-29 15:30 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-01-29 17:08 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-01-29 17:21 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-01-29 17:37 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-01-30 08:45 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-01-30 09:23 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-01-30 09:44 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-01-30 09:52 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-01-30 10:02 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-01-30 10:10 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-01-29 18:16 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2020-01-30 09:33 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-01-31 19:38 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2020-02-03 08:08 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-02-04 21:38 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2020-02-05 07:28 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-02-05 14:10 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2020-02-05 15:48 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-02-05 19:18 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-02-07 15:15 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2020-02-07 17:28 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-02-07 17:31 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2020-02-07 18:37 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-02-09 12:05 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-02-09 12:53 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-02-16 15:07 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-29 13:43 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2020-01-28 16:01 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-28 16:40 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-01-28 17:12 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-28 17:13 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-01-28 17:54 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-01-29 13:06 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-30 14:06 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-29 13:48 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2020-01-30 14:17 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-30 14:21 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-02-01 13:39 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-02-01 18:00 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-02-03 13:03 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-02-03 19:46 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-02-14 09:19 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-02-15 09:56 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-02-15 10:06 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-02-16 15:16 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-02-16 15:22 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-02-21 07:45 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-02-21 15:40 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-02-24 09:44 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-02-24 13:34 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-02-24 13:41 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-02-25 08:53 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-02-24 13:57 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-02-25 08:55 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-02-25 13:36 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-02-25 13:49 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-02-25 15:31 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-02-26 15:54 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-02-25 13:56 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-02-26 15:52 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-02-27 04:12 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-02-27 04:13 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-03-02 14:47 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-03-03 08:40 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-03-04 07:49 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-03-06 07:00 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-03-04 15:39 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-03-06 07:05 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-03-09 13:34 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-03-09 16:29 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-03-09 14:37 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-03-09 16:31 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-03-11 07:52 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-03-11 13:06 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-03-11 20:12 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2020-03-12 10:06 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-03-12 12:22 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-03-17 03:42 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-03-17 03:45 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-03-24 13:34 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-03-25 12:52 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-03-25 12:57 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-03-26 03:15 ` Re: [Proposal] Global temporary tables wjzeng <wenjing.zwj@alibaba-inc.com>
2020-03-26 03:36 ` Re: [Proposal] Global temporary tables wjzeng <wenjing.zwj@alibaba-inc.com>
2020-03-26 04:34 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-03-27 05:25 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-03-27 09:21 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-03-31 04:16 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-03-31 13:59 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-04-01 03:22 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-01 03:47 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-04-01 12:56 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-04-02 08:45 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-04-02 08:52 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-04-03 07:52 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-03 08:38 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-04-03 12:43 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-04-07 08:51 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-07 10:22 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-04-08 08:18 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-08 10:55 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-04-09 12:55 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-22 09:19 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-22 14:38 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-04-22 14:50 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-04-23 07:10 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-23 07:43 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-04-24 04:55 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-04-24 07:28 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-04-27 09:46 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-27 09:45 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-26 08:09 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-27 09:26 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-04-27 12:04 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-27 13:48 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-04-29 03:22 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-29 11:46 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-05-07 11:12 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-06-09 12:15 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-06-11 02:13 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-07-06 15:31 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-07-07 03:47 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-07-07 10:05 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-07-10 09:03 ` Re: [Proposal] Global temporary tables wenjing zeng <wjzeng2012@gmail.com>
2020-07-13 11:59 ` Re: [Proposal] Global temporary tables wenjing zeng <wjzeng2012@gmail.com>
2020-07-14 14:11 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-07-14 14:28 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-07-22 11:53 ` Re: [Proposal] Global temporary tables wenjing zeng <wjzeng2012@gmail.com>
2020-07-23 06:54 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-07-30 12:09 ` Re: [Proposal] Global temporary tables wenjing zeng <wjzeng2012@gmail.com>
2020-07-30 20:57 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2020-08-07 08:26 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-09-11 15:00 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-11-20 18:28 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-11-23 09:27 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-11-25 06:19 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-11-25 13:08 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2021-03-16 18:05 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2021-03-17 11:59 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-03-28 07:27 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2021-03-28 08:49 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2021-03-29 09:55 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2021-03-28 13:07 ` Re: [Proposal] Global temporary tables Andrew Dunstan <andrew@dunslane.net>
2021-03-29 08:37 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2021-03-29 11:34 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2021-03-29 11:44 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-03-30 06:08 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2021-04-15 07:26 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-04-15 08:48 ` Re: [Proposal] Global temporary tables shawn wang <shawn.wang.pg@gmail.com>
2021-04-22 07:41 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-05-10 10:43 ` Re: [Proposal] Global temporary tables Dilip Kumar <dilipbalaut@gmail.com>
2021-05-12 12:39 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-06-04 10:01 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-07-14 02:56 ` Re: [Proposal] Global temporary tables Ming Li <mli@apache.org>
2021-07-22 09:40 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-07-28 15:09 ` Re: [Proposal] Global temporary tables Tony Zhu <tony.zhu@ww-it.cn>
2021-07-29 15:19 ` Re: [Proposal] Global temporary tables wenjing zeng <wjzeng2012@gmail.com>
2021-08-05 07:58 ` Re: [Proposal] Global temporary tables ZHU XIAN WEN <tony.zhu@ww-it.cn>
2021-09-16 06:29 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2021-09-23 03:48 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-09-23 04:03 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-03-29 11:27 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2021-09-23 07:22 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-09-23 13:55 ` Re: [Proposal] Global temporary tables Tony Zhu <tony.zhu@ww-it.cn>
2021-09-26 04:04 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-09-29 05:53 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2021-10-03 13:15 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-10-06 14:09 ` Re: [Proposal] Global temporary tables Andrew Bille <andrewbille@gmail.com>
2021-10-09 07:41 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-10-13 05:08 ` Re: [Proposal] Global temporary tables Andrew Bille <andrewbille@gmail.com>
2021-10-14 08:28 ` Re: [Proposal] Global temporary tables wenjing zeng <wjzeng2012@gmail.com>
2021-10-15 07:44 ` Re: [Proposal] Global temporary tables Andrew Bille <andrewbille@gmail.com>
2021-10-18 12:00 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-10-19 07:39 ` Re: [Proposal] Global temporary tables Andrew Bille <andrewbille@gmail.com>
2021-10-21 09:25 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-10-22 09:11 ` Re: [Proposal] Global temporary tables Andrew Bille <andrewbille@gmail.com>
2021-10-25 12:13 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-10-28 10:30 ` Re: [Proposal] Global temporary tables Andrew Bille <andrewbille@gmail.com>
2021-10-29 17:28 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-11-09 08:51 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-11-11 08:15 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-11-15 10:33 ` Re: [Proposal] Global temporary tables Andrew Bille <andrewbille@gmail.com>
2021-11-19 17:31 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-12-20 12:42 ` Re: [Proposal] Global temporary tables wenjing zeng <wjzeng2012@gmail.com>
2021-12-21 03:04 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-12-21 05:59 ` Re: [Proposal] Global temporary tables Andrew Bille <andrewbille@gmail.com>
2021-12-23 12:36 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2022-01-10 09:17 ` Re: [Proposal] Global temporary tables Andrew Bille <andrewbille@gmail.com>
2022-01-20 09:53 ` Re: [Proposal] Global temporary tables Wenjing Zeng <wjzeng2012@gmail.com>
2022-02-15 09:03 ` Re: [Proposal] Global temporary tables Wenjing Zeng <wjzeng2012@gmail.com>
2022-02-25 06:26 ` Re: [Proposal] Global temporary tables Wenjing Zeng <wjzeng2012@gmail.com>
2022-02-25 07:45 ` Re: [Proposal] Global temporary tables Andres Freund <andres@anarazel.de>
2022-02-28 12:08 ` Re: [Proposal] Global temporary tables Wenjing Zeng <wjzeng2012@gmail.com>
2022-02-27 00:21 ` Re: [Proposal] Global temporary tables Justin Pryzby <pryzby@telsasoft.com>
2022-02-27 03:17 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2022-02-27 04:13 ` Re: [Proposal] Global temporary tables Andres Freund <andres@anarazel.de>
2022-02-27 05:09 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2022-03-02 21:17 ` Re: [Proposal] Global temporary tables Andres Freund <andres@anarazel.de>
2022-03-03 01:35 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2022-03-03 16:22 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2022-03-03 20:28 ` Re: [Proposal] Global temporary tables Greg Stark <stark@mit.edu>
2022-03-03 21:07 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2022-03-03 21:20 ` Re: [Proposal] Global temporary tables Andres Freund <andres@anarazel.de>
2022-06-30 20:54 ` Re: [Proposal] Global temporary tables Jacob Champion <jchampion@timescale.com>
2022-03-02 02:52 ` Re: [Proposal] Global temporary tables Wenjing Zeng <wjzeng2012@gmail.com>
2022-03-02 18:02 ` Re: [Proposal] Global temporary tables Adam Brusselback <adambrusselback@gmail.com>
2022-03-02 18:08 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2022-03-01 07:10 ` Re: [Proposal] Global temporary tables Wenjing Zeng <wjzeng2012@gmail.com>
2020-11-26 02:54 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-11-26 10:16 ` Re: [Proposal] Global temporary tables Julien Rouhaud <rjuju123@gmail.com>
2020-11-26 10:44 ` Re: [Proposal] Global temporary tables Magnus Hagander <magnus@hagander.net>
2020-04-23 06:21 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-24 13:03 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-04-27 09:49 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-03-27 10:06 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-03-31 04:11 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-03-25 10:44 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-03-26 03:37 ` Re: [Proposal] Global temporary tables wjzeng <wenjing.zwj@alibaba-inc.com>
2020-03-25 14:16 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-03-26 03:38 ` Re: [Proposal] Global temporary tables wjzeng <wenjing.zwj@alibaba-inc.com>
2020-03-13 12:40 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-03-13 14:16 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-03-16 06:23 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-03-16 07:59 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-03-16 09:31 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-03-16 09:57 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-03-16 08:05 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-03-16 08:58 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-03-16 09:04 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-03-16 09:26 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-03-16 09:24 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-03-19 10:21 ` Re: [Proposal] Global temporary tables wenjing.zwj <wenjing.zwj@alibaba-inc.com>
2020-03-19 12:39 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-03-17 03:44 ` [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-03-05 14:19 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-03-05 14:38 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2020-03-06 07:10 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-03-09 12:24 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-03-09 16:28 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-03-06 07:08 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-04-07 08:57 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-07 10:40 ` Re: [Proposal] Global temporary tables Erik Rijkers <er@xs4all.nl>
2020-04-09 13:28 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-09 13:38 ` Re: [Proposal] Global temporary tables Erik Rijkers <er@xs4all.nl>
2020-04-07 11:25 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-04-09 12:56 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-13 10:32 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-04-17 09:14 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-17 11:26 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-04-17 12:59 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-04-20 09:09 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-20 09:29 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-20 13:15 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-04-21 06:19 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-08-03 07:09 ` Re: [Proposal] Global temporary tables movead.li@highgo.ca <movead.li@highgo.ca>
2020-08-07 08:17 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-08-07 09:30 ` Re: [Proposal] Global temporary tables movead.li@highgo.ca <movead.li@highgo.ca>
2020-08-12 09:51 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-20 09:31 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-20 10:18 ` Re: [Proposal] Global temporary tables Prabhat Sahu <prabhat.sahu@enterprisedb.com>
2020-04-08 10:34 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-04-13 08:27 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-04-13 08:37 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-04-09 11:46 ` Re: [Proposal] Global temporary tables tushar <tushar.ahuja@enterprisedb.com>
2020-04-09 12:58 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-01-11 14:00 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-11 20:27 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-01-14 13:09 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-14 13:20 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-01-14 14:16 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-19 17:04 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-19 17:32 ` Re: [Proposal] Global temporary tables Erik Rijkers <er@xs4all.nl>
2020-01-20 16:27 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-21 05:43 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-01-22 05:29 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-23 16:28 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-23 17:21 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-01-24 08:20 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-01-25 15:15 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-27 09:38 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-02-01 16:14 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-02-03 08:16 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-02-04 15:01 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-02-04 16:47 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2020-02-05 13:20 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-02-05 14:15 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2020-02-06 03:39 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-02-04 20:57 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2020-02-05 09:51 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-21 08:45 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-21 18:51 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-01-22 06:16 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2020-01-22 06:31 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-01-23 16:22 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2019-10-28 07:15 ` Re: [Proposal] Global temporary tables =?UTF-8?B?5pu+5paH5peMKOS5ieS7jik=?= <wenjing.zwj@alibaba-inc.com>
2019-10-28 12:13 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2019-10-28 13:48 ` Re: [Proposal] Global temporary tables Konstantin Knizhnik <k.knizhnik@postgrespro.ru>
2019-10-28 16:29 ` Re: [Proposal] Global temporary tables Robert Haas <robertmhaas@gmail.com>
2020-11-26 11:46 [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-12-08 05:28 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-12-09 06:33 ` Re: [Proposal] Global temporary tables 曾文旌 <wenjing.zwj@alibaba-inc.com>
2020-12-09 09:07 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2020-12-17 03:06 ` Re: [Proposal] Global temporary tables wenjing zeng <wjzeng2012@gmail.com>
2020-12-22 03:20 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-01-25 18:02 ` Re: [Proposal] Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2021-02-01 08:49 ` Re: [Proposal] Global temporary tables wenjing <wjzeng2012@gmail.com>
2021-02-24 16:59 ` Re: [Proposal] Global temporary tables Alexandre Arruda <adaldeia@gmail.com>
2021-02-24 18:59 ` Re: [Proposal] Global temporary tables Alexandre Arruda <adaldeia@gmail.com>
2026-06-21 19:08 Global temporary tables Dean Rasheed <dean.a.rasheed@gmail.com>
2026-06-21 22:05 ` Re: Global temporary tables Andrew Dunstan <andrew@dunslane.net>
2026-06-22 08:56 ` Re: Global temporary tables Dean Rasheed <dean.a.rasheed@gmail.com>
2026-06-22 13:43 ` Re: Global temporary tables Andrew Dunstan <andrew@dunslane.net>
2026-06-22 15:30 ` Re: Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2026-06-23 09:12 ` Re: Global temporary tables Dean Rasheed <dean.a.rasheed@gmail.com>
2026-06-23 23:34 ` Re: Global temporary tables Dean Rasheed <dean.a.rasheed@gmail.com>
2026-06-24 04:15 ` Re: Global temporary tables Gilles Darold <gilles@darold.net>
2026-06-26 03:51 ` Re: Global temporary tables Kirk Wolak <wolakk@gmail.com>
2026-06-26 19:37 ` Re: Global temporary tables Dean Rasheed <dean.a.rasheed@gmail.com>
2026-06-26 21:37 ` Re: Global temporary tables Kirk Wolak <wolakk@gmail.com>
2026-06-30 19:29 ` Re: Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2026-06-30 20:21 ` Re: Global temporary tables Dean Rasheed <dean.a.rasheed@gmail.com>
2026-06-30 20:32 ` Re: Global temporary tables Pavel Stehule <pavel.stehule@gmail.com>
2026-06-30 21:41 ` Re: Global temporary tables Haibo Yan <tristan.yim@gmail.com>
2026-07-01 07:28 ` Re: Global temporary tables Dean Rasheed <dean.a.rasheed@gmail.com>
2026-07-01 06:24 ` Re: Global temporary tables Japin Li <japinli@hotmail.com>
2026-07-01 07:32 ` Re: Global temporary tables Dean Rasheed <dean.a.rasheed@gmail.com>
2026-07-02 09:11 ` Re: Global temporary tables Japin Li <japinli@hotmail.com>
2026-07-03 03:16 ` Re: Global temporary tables Japin Li <japinli@hotmail.com>
2026-07-01 23:20 ` Re: Global temporary tables Nico Williams <nico@cryptonector.com>
2026-07-03 23:32 ` Re: Global temporary tables Dean Rasheed <dean.a.rasheed@gmail.com>
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox