Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1jTdJL-00071D-64 for pgsql-hackers@arkaria.postgresql.org; Wed, 29 Apr 2020 03:23:00 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1jTdJK-00042t-2j for pgsql-hackers@arkaria.postgresql.org; Wed, 29 Apr 2020 03:22:58 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1jTdJJ-00042l-9s for pgsql-hackers@lists.postgresql.org; Wed, 29 Apr 2020 03:22:57 +0000 Received: from out0-139.mail.aliyun.com ([140.205.0.139]) by magus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1jTdJC-0001Aq-SH for pgsql-hackers@postgresql.org; Wed, 29 Apr 2020 03:22:56 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=alibaba-inc.com; s=default; t=1588130565; h=From:Message-Id:Content-Type:Mime-Version:Subject:Date:To; bh=Ux27wGxXfVJ82X3Y8/9cU5hDpiNrYEvQ1naMSM7IrJo=; b=TiMxj865UX/ur5wSOWkfKBTOUS2FHAV0bvxJ71nhmCoNKBMyUM5RFdvNlriCVsb7V+ixwV1E1aGG0TeQr7eLzOJIE986HhhyY3qqFCClX4yqBHwJChkIk6ZGXnp+oskYCe3hvUMuFrr5EgYEwtr6jOzofpSFK6MDew2EDrZQpvQ= X-Alimail-AntiSpam: AC=PASS;BC=-1|-1;BR=01201311R181e4;CH=green;DM=||false|;DS=||;FP=0|-1|-1|-1|0|-1|-1|-1;HT=e02c03297;MF=wenjing.zwj@alibaba-inc.com;NM=1;PH=DS;RN=9;SR=0;TI=SMTPD_---.HPo-551_1588130563; Received: from 30.225.184.56(mailfrom:wenjing.zwj@alibaba-inc.com fp:SMTPD_---.HPo-551_1588130563) by smtp.aliyun-inc.com(127.0.0.1); Wed, 29 Apr 2020 11:22:43 +0800 From: =?utf-8?B?5pu+5paH5peM?= Message-Id: Content-Type: multipart/signed; boundary="Apple-Mail=_BBA48AD8-6132-4D22-9678-E18AD16EFAED"; protocol="application/pkcs7-signature"; micalg=sha-256 Mime-Version: 1.0 (Mac OS X Mail 13.4 \(3608.80.23.2.2\)) Subject: Re: [Proposal] Global temporary tables Date: Wed, 29 Apr 2020 11:22:42 +0800 In-Reply-To: Cc: Pavel Stehule , tushar , Robert Haas , Tomas Vondra , Konstantin Knizhnik , PostgreSQL Hackers , =?utf-8?B?IuiUoeadvumcsijlrZDlmIkpIg==?= , "Cai, Le" To: Prabhat Sahu References: <24099f4e-742c-e92b-729c-77bac1ca80d4@postgrespro.ru> <77320A29-DC39-4EFE-A5E2-0A00F6A316E8@alibaba-inc.com> <7eb93026-0f35-325a-132f-ca183a5da3ac@enterprisedb.com> <7CCE7DE5-5E85-4155-9010-5EDEAE762194@alibaba-inc.com> <087bd809-e1d7-ee44-da1f-25996abad442@enterprisedb.com> <40391A22-CB8B-4143-B936-7707667F7905@alibaba-inc.com> <68FC0EB3-8E39-4300-B9ED-036F4163C791@alibaba-inc.com> <25D7E36F-B2D6-4A63-BFC7-E1FD24EA6BE0@alibaba-inc.com> <507b246f-4350-f4af-3950-a334be7c2cf2@enterprisedb.com> <06844F6A-0BD2-438E-95E5-E76E4CB5CA57@alibaba-inc.com> <750A0445-7FE0-4B4D-A319-AC1A8C7249EB@alibaba-inc.com> <9EBDB104-42CD-409E-BB27-D14AB0858AE4@alibaba-inc.com> <6FBF19E7-1C3E-4F83-AA37-778F2A285AD9@alibaba-inc.com> <318BAF11-FE5A-4CE0-9286-3695F9AD0A8A@alibaba-inc.com> X-Mailer: Apple Mail (2.3608.80.23.2.2) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk --Apple-Mail=_BBA48AD8-6132-4D22-9678-E18AD16EFAED Content-Type: multipart/alternative; boundary="Apple-Mail=_CCA669D4-0143-4B16-8D35-9EA7101C37C2" --Apple-Mail=_CCA669D4-0143-4B16-8D35-9EA7101C37C2 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 > 2020=E5=B9=B44=E6=9C=8827=E6=97=A5 =E4=B8=8B=E5=8D=889:48=EF=BC=8CPrabha= t Sahu =E5=86=99=E9=81=93=EF=BC=9A >=20 > Thanks Wenjing, for the fix patch for previous issues. > I have verified the issues, now those fix look good to me. > But the below error message is confusing(for gtt2). >=20 > postgres=3D# drop table gtt1; > ERROR: cannot drop global temp table gtt1 when other backend attached = it. >=20 > postgres=3D# drop table gtt2; > ERROR: cannot drop index idx2 on global temp table gtt2 when other = backend attached it. >=20 > I feel the above error message shown for "DROP TABLE gtt2;" is a bit = confusing(looks similar to DROP INDEX gtt2;). > If possible, can we keep the error message simple as "ERROR: cannot = drop global temp table gtt2 when other backend attached it."? > I mean, without giving extra information for the index attached to = that GTT. Fixed the error message to make the expression more accurate. In v33. Wenjing >=20 > On Mon, Apr 27, 2020 at 5:34 PM =E6=9B=BE=E6=96=87=E6=97=8C = > = wrote: >=20 >=20 >> 2020=E5=B9=B44=E6=9C=8827=E6=97=A5 =E4=B8=8B=E5=8D=885:26=EF=BC=8CPrabh= at Sahu > =E5=86=99=E9=81=93=EF=BC=9A >>=20 >> Hi Wenjing, >>=20 >> Please check the below scenario shows different error message with = "DROP TABLE gtt;" for gtt with and without index. >> -- Session1: >> postgres=3D# create global temporary table gtt1 (c1 int); >> CREATE TABLE >> postgres=3D# create global temporary table gtt2 (c1 int); >> CREATE TABLE >> postgres=3D# create index idx2 on gtt2(c1); >> CREATE INDEX >>=20 >> -- Session2: >> postgres=3D# drop table gtt1; >> ERROR: can not drop relation gtt1 when other backend attached this = global temp table >> postgres=3D# drop table gtt2; >> ERROR: can not drop index gtt2 when other backend attached this = global temp table. > For DROP GTT, we need to drop the index on the table first.=20 > So the indexes on the GTT are checked first. > But the error message needs to be fixed. > Fixed in v32 >=20 >=20 > wenjing >=20 >=20 >=20 >=20 >>=20 >> --=20 >> With Regards, >> Prabhat Kumar Sahu >> EnterpriseDB: http://www.enterprisedb.com = >=20 >=20 >=20 > --=20 > With Regards, > Prabhat Kumar Sahu > EnterpriseDB: http://www.enterprisedb.com = --Apple-Mail=_CCA669D4-0143-4B16-8D35-9EA7101C37C2 Content-Type: multipart/mixed; boundary="Apple-Mail=_C4CEB335-6ADF-4AEA-B7D4-5620CD4B6984" --Apple-Mail=_C4CEB335-6ADF-4AEA-B7D4-5620CD4B6984 Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset=utf-8

2020=E5=B9=B44=E6=9C=8827=E6=97=A5 =E4=B8=8B=E5=8D=889:48=EF=BC= =8CPrabhat Sahu <prabhat.sahu@enterprisedb.com> =E5=86=99=E9=81=93=EF=BC=9A=

Thanks = Wenjing, for the fix patch for previous issues.
I have = verified the issues, now those fix look good to me.
But the = below error message is confusing(for gtt2).

postgres=3D# drop table gtt1;
ERROR:  cannot drop global temp table gtt1 when other = backend attached it.

postgres=3D# drop table gtt2;
ERROR: =  cannot drop index idx2 on global temp table gtt2 when other = backend attached it.

I feel the above error message = shown for "DROP TABLE gtt2;" is a bit confusing(looks similar to DROP = INDEX gtt2;).
If = possible, can we keep the error message simple as "ERROR:  cannot drop global temp table = gtt2 when other backend attached it."?
I mean, without giving extra = information for the index attached to that = GTT.
Fixed the error message to = make the expression more accurate. In v33.


Wenjing


= --Apple-Mail=_C4CEB335-6ADF-4AEA-B7D4-5620CD4B6984 Content-Disposition: attachment; filename=global_temporary_table_v33-pg13.patch Content-Type: application/octet-stream; x-unix-mode=0644; name="global_temporary_table_v33-pg13.patch" Content-Transfer-Encoding: 7bit diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 8ccc228..b061de9 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] = }, true }, + /* + * For global temp table only + * use ShareUpdateExclusiveLock for ensure safety + */ + { + { + "on_commit_delete_rows", + "global temp table on commit options", + RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED, + ShareUpdateExclusiveLock + }, + true + }, /* list terminator */ {{NULL}} }; @@ -1817,6 +1830,8 @@ bytea * default_reloptions(Datum reloptions, bool validate, relopt_kind kind) { static const relopt_parse_elt tab[] = { + {"on_commit_delete_rows", RELOPT_TYPE_BOOL, + offsetof(StdRdOptions, on_commit_delete_rows)}, {"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)}, {"autovacuum_enabled", RELOPT_TYPE_BOOL, offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)}, @@ -1961,13 +1976,18 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) bytea * partitioned_table_reloptions(Datum reloptions, bool validate) { + static const relopt_parse_elt tab[] = { + {"on_commit_delete_rows", RELOPT_TYPE_BOOL, + offsetof(StdRdOptions, on_commit_delete_rows)} + }; + /* * There are no options for partitioned tables yet, but this is able to do * some validation. */ return (bytea *) build_reloptions(reloptions, validate, RELOPT_KIND_PARTITIONED, - 0, NULL, 0); + sizeof(StdRdOptions), tab, lengthof(tab)); } /* diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 765329b..4761fdc 100644 --- a/src/backend/access/gist/gistutil.c +++ b/src/backend/access/gist/gistutil.c @@ -1011,7 +1011,9 @@ gistproperty(Oid index_oid, int attno, XLogRecPtr gistGetFakeLSN(Relation rel) { - if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + /* global temp is same as local temp table */ + if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP || + rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) { /* * Temporary relations are only accessible in our session, so a simple diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index 3ec6d52..1222594 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -150,7 +150,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo) * metapage, nor the first bitmap page. */ sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ; - if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP) + /* global temp table is same as local temp table */ + if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP || + index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)) sort_threshold = Min(sort_threshold, NBuffers); else sort_threshold = Min(sort_threshold, NLocBuffer); diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 56b3562..d3443ec 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -587,7 +587,7 @@ heapam_relation_set_new_filenode(Relation rel, */ *minmulti = GetOldestMultiXactId(); - srel = RelationCreateStorage(*newrnode, persistence); + srel = RelationCreateStorage(*newrnode, persistence, rel); /* * If required, set up an init fork for an unlogged table so that it can @@ -640,7 +640,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode) * NOTE: any conflict in relfilenode value will be caught in * RelationCreateStorage(). */ - RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence); + RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel); /* copy main fork */ RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM, diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 3c18db2..b08a1fc 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -62,6 +62,7 @@ #include "access/xact.h" #include "access/xlog.h" #include "catalog/storage.h" +#include "catalog/storage_gtt.h" #include "commands/dbcommands.h" #include "commands/progress.h" #include "commands/vacuum.h" @@ -431,9 +432,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params, Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT); Assert(params->truncate != VACOPT_TERNARY_DEFAULT); - /* not every AM requires these to be valid, but heap does */ - Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid)); - Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid)); + /* not every AM requires these to be valid, but regular heap does */ + Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid)); + Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid)); /* measure elapsed time iff autovacuum logging requires it */ if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0) diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c index f5962f6..045c0ce 100644 --- a/src/backend/access/nbtree/nbtpage.c +++ b/src/backend/access/nbtree/nbtpage.c @@ -28,6 +28,7 @@ #include "access/transam.h" #include "access/xlog.h" #include "access/xloginsert.h" +#include "catalog/storage_gtt.h" #include "miscadmin.h" #include "storage/indexfsm.h" #include "storage/lmgr.h" @@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel) { Buffer metabuf; + if (RELATION_IS_GLOBAL_TEMP(rel) && + !gtt_storage_attached(RelationGetRelid(rel))) + return 0; + metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ); metad = _bt_getmeta(rel, metabuf); diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 065eb27..8427894 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -6433,6 +6433,10 @@ StartupXLOG(void) else recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID; + /* clean temp relation files */ + if (max_active_gtt > 0) + RemovePgTempFiles(); + /* * Check for signal files, and if so set up state for offline recovery */ diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y index 5eaca27..e5002aa 100644 --- a/src/backend/bootstrap/bootparse.y +++ b/src/backend/bootstrap/bootparse.y @@ -231,7 +231,8 @@ Boot_CreateStmt: mapped_relation, true, &relfrozenxid, - &relminmxid); + &relminmxid, + false); elog(DEBUG4, "bootstrap relation created"); } else diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index 9499bb3..ae47364 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -43,6 +43,7 @@ OBJS = \ pg_subscription.o \ pg_type.o \ storage.o \ + storage_gtt.o \ toasting.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c index 7d6acae..3be8d63 100644 --- a/src/backend/catalog/catalog.c +++ b/src/backend/catalog/catalog.c @@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence) switch (relpersistence) { + /* global temp table is same as local temp table */ case RELPERSISTENCE_TEMP: + case RELPERSISTENCE_GLOBAL_TEMP: backend = BackendIdForTempRelations(); break; case RELPERSISTENCE_UNLOGGED: diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 632c058..9b43493 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -61,6 +61,7 @@ #include "catalog/pg_type.h" #include "catalog/storage.h" #include "catalog/storage_xlog.h" +#include "catalog/storage_gtt.h" #include "commands/tablecmds.h" #include "commands/typecmds.h" #include "executor/executor.h" @@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc, Oid reloftype, Oid relowner, char relkind, + char relpersistence, TransactionId relfrozenxid, TransactionId relminmxid, Datum relacl, @@ -304,7 +306,8 @@ heap_create(const char *relname, bool mapped_relation, bool allow_system_table_mods, TransactionId *relfrozenxid, - MultiXactId *relminmxid) + MultiXactId *relminmxid, + bool skip_create_storage) { bool create_storage; Relation rel; @@ -404,6 +407,9 @@ heap_create(const char *relname, relpersistence, relkind); + if (skip_create_storage) + create_storage = false; + /* * Have the storage manager create the relation's disk file, if needed. * @@ -427,7 +433,7 @@ heap_create(const char *relname, case RELKIND_INDEX: case RELKIND_SEQUENCE: - RelationCreateStorage(rel->rd_node, relpersistence); + RelationCreateStorage(rel->rd_node, relpersistence, rel); break; case RELKIND_RELATION: @@ -959,6 +965,7 @@ AddNewRelationTuple(Relation pg_class_desc, Oid reloftype, Oid relowner, char relkind, + char relpersistence, TransactionId relfrozenxid, TransactionId relminmxid, Datum relacl, @@ -997,8 +1004,18 @@ AddNewRelationTuple(Relation pg_class_desc, break; } - new_rel_reltup->relfrozenxid = relfrozenxid; - new_rel_reltup->relminmxid = relminmxid; + /* global temp table not remember transaction info in catalog */ + if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + { + new_rel_reltup->relfrozenxid = InvalidTransactionId; + new_rel_reltup->relminmxid = InvalidMultiXactId; + } + else + { + new_rel_reltup->relfrozenxid = relfrozenxid; + new_rel_reltup->relminmxid = relminmxid; + } + new_rel_reltup->relowner = relowner; new_rel_reltup->reltype = new_type_oid; new_rel_reltup->reloftype = reloftype; @@ -1261,7 +1278,8 @@ heap_create_with_catalog(const char *relname, mapped_relation, allow_system_table_mods, &relfrozenxid, - &relminmxid); + &relminmxid, + false); Assert(relid == RelationGetRelid(new_rel_desc)); @@ -1360,6 +1378,7 @@ heap_create_with_catalog(const char *relname, reloftypeid, ownerid, relkind, + relpersistence, relfrozenxid, relminmxid, PointerGetDatum(relacl), @@ -1940,6 +1959,16 @@ heap_drop_with_catalog(Oid relid) if (relid == defaultPartOid) update_default_partition_oid(parentOid, InvalidOid); + /* We allow to drop global temp table only this session use it */ + if (RELATION_IS_GLOBAL_TEMP(rel)) + { + if (is_other_backend_use_gtt(RelationGetRelid(rel))) + ereport(ERROR, + (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), + errmsg("cannot drop global temporary table %s when other backend attached it.", + RelationGetRelationName(rel)))); + } + /* * Schedule unlinking of the relation's physical files at commit. */ @@ -3167,7 +3196,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum) * the specified relation. Caller must hold exclusive lock on rel. */ static void -RelationTruncateIndexes(Relation heapRelation) +RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode) { ListCell *indlist; @@ -3179,7 +3208,7 @@ RelationTruncateIndexes(Relation heapRelation) IndexInfo *indexInfo; /* Open the index relation; use exclusive lock, just to be sure */ - currentIndex = index_open(indexId, AccessExclusiveLock); + currentIndex = index_open(indexId, lockmode); /* * Fetch info needed for index_build. Since we know there are no @@ -3225,8 +3254,13 @@ heap_truncate(List *relids) { Oid rid = lfirst_oid(cell); Relation rel; + LOCKMODE lockmode = AccessExclusiveLock; + + /* truncate global temp table only need RowExclusiveLock */ + if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP) + lockmode = RowExclusiveLock; - rel = table_open(rid, AccessExclusiveLock); + rel = table_open(rid, lockmode); relations = lappend(relations, rel); } @@ -3259,6 +3293,7 @@ void heap_truncate_one_rel(Relation rel) { Oid toastrelid; + LOCKMODE lockmode = AccessExclusiveLock; /* * Truncate the relation. Partitioned tables have no storage, so there is @@ -3267,23 +3302,37 @@ heap_truncate_one_rel(Relation rel) if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) return; + if (RELATION_IS_GLOBAL_TEMP(rel)) + { + if (!gtt_storage_attached(RelationGetRelid(rel))) + return; + + /* + * Truncate global temp table only need RowExclusiveLock + */ + lockmode = RowExclusiveLock; + } + /* Truncate the underlying relation */ table_relation_nontransactional_truncate(rel); /* If the relation has indexes, truncate the indexes too */ - RelationTruncateIndexes(rel); + RelationTruncateIndexes(rel, lockmode); /* If there is a toast table, truncate that too */ toastrelid = rel->rd_rel->reltoastrelid; if (OidIsValid(toastrelid)) { - Relation toastrel = table_open(toastrelid, AccessExclusiveLock); + Relation toastrel = table_open(toastrelid, lockmode); table_relation_nontransactional_truncate(toastrel); - RelationTruncateIndexes(toastrel); + RelationTruncateIndexes(toastrel, lockmode); /* keep the lock... */ table_close(toastrel, NoLock); } + + if (RELATION_IS_GLOBAL_TEMP(rel)) + up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId); } /* diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 7cfbdd5..2c09b94 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -53,6 +53,7 @@ #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" #include "catalog/storage.h" +#include "catalog/storage_gtt.h" #include "commands/event_trigger.h" #include "commands/progress.h" #include "commands/tablecmds.h" @@ -727,6 +728,11 @@ index_create(Relation heapRelation, char relkind; TransactionId relfrozenxid; MultiXactId relminmxid; + bool skip_create_storage = false; + + if (RELATION_IS_GLOBAL_TEMP(heapRelation) && + !gtt_storage_attached(RelationGetRelid(heapRelation))) + skip_create_storage = true; /* constraint flags can only be set when a constraint is requested */ Assert((constr_flags == 0) || @@ -880,6 +886,19 @@ index_create(Relation heapRelation, indexRelationName, RelationGetRelationName(heapRelation)))); } + if (RELATION_IS_GLOBAL_TEMP(heapRelation)) + { + /* No support create index on global temp table use concurrent mode yet */ + if (concurrent) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot reindex global temporary tables concurrently"))); + + /* if global temp table not init storage, then skip build index */ + if (!gtt_storage_attached(RelationGetRelid(heapRelation))) + flags |= INDEX_CREATE_SKIP_BUILD; + } + /* * construct tuple descriptor for index tuples */ @@ -934,7 +953,8 @@ index_create(Relation heapRelation, mapped_relation, allow_system_table_mods, &relfrozenxid, - &relminmxid); + &relminmxid, + skip_create_storage); Assert(relfrozenxid == InvalidTransactionId); Assert(relminmxid == InvalidMultiXactId); @@ -2040,7 +2060,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode) * lock (see comments in RemoveRelations), and a non-concurrent DROP is * more efficient. */ - Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP || + Assert(!(get_rel_persistence(indexId) == RELPERSISTENCE_TEMP || + get_rel_persistence(indexId) == RELPERSISTENCE_GLOBAL_TEMP) || (!concurrent && !concurrent_lock_mode)); /* @@ -2072,6 +2093,17 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode) */ CheckTableNotInUse(userIndexRelation, "DROP INDEX"); + /* We allow to drop index on global temp table only this session use it */ + if (RELATION_IS_GLOBAL_TEMP(userHeapRelation)) + { + if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation))) + ereport(ERROR, + (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), + errmsg("cannot drop index %s or global temporary table %s", + RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)), + errhint("Because the index is created on the global temporary table and other backend attached it."))); + } + /* * Drop Index Concurrently is more or less the reverse process of Create * Index Concurrently. @@ -2680,6 +2712,11 @@ index_update_stats(Relation rel, HeapTuple tuple; Form_pg_class rd_rel; bool dirty; + bool is_gtt = false; + + /* update index stats into localhash and rel_rd_rel for global temp table */ + if (RELATION_IS_GLOBAL_TEMP(rel)) + is_gtt = true; /* * We always update the pg_class row using a non-transactional, @@ -2765,21 +2802,35 @@ index_update_stats(Relation rel, else /* don't bother for indexes */ relallvisible = 0; - if (rd_rel->relpages != (int32) relpages) + if (is_gtt) + rel->rd_rel->relpages = (int32) relpages; + else if (rd_rel->relpages != (int32) relpages) { rd_rel->relpages = (int32) relpages; dirty = true; } - if (rd_rel->reltuples != (float4) reltuples) + + if (is_gtt) + rel->rd_rel->reltuples = (float4) reltuples; + else if (rd_rel->reltuples != (float4) reltuples) { rd_rel->reltuples = (float4) reltuples; dirty = true; } - if (rd_rel->relallvisible != (int32) relallvisible) + + if (is_gtt) + rel->rd_rel->relallvisible = (int32) relallvisible; + else if (rd_rel->relallvisible != (int32) relallvisible) { rd_rel->relallvisible = (int32) relallvisible; dirty = true; } + + if (is_gtt) + { + up_gtt_relstats(rel, relpages, reltuples, relallvisible, + InvalidTransactionId, InvalidMultiXactId); + } } /* @@ -2893,6 +2944,15 @@ index_build(Relation heapRelation, pgstat_progress_update_multi_param(6, index, val); } + if (RELATION_IS_GLOBAL_TEMP(indexRelation)) + { + if (!gtt_storage_attached(RelationGetRelid(indexRelation))) + { + gtt_force_enable_index(indexRelation); + RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation); + } + } + /* * Call the access method's build procedure */ @@ -3448,6 +3508,16 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence, * we only need to be sure no schema or data changes are going on. */ heapId = IndexGetRelation(indexId, false); + + if (persistence == RELPERSISTENCE_GLOBAL_TEMP && + !gtt_storage_attached(indexId)) + { + /* Correction status flag, and return */ + SetReindexProcessing(heapId, indexId); + ResetReindexProcessing(); + return; + } + heapRelation = table_open(heapId, ShareLock); if (progress) diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 2ec2301..1b6061d 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid) errmsg("cannot create temporary relation in non-temporary schema"))); } break; + /* global temp table */ + case RELPERSISTENCE_GLOBAL_TEMP: + if (isAnyTempNamespace(nspid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot create global temp relations in temporary schemas"))); + break; case RELPERSISTENCE_PERMANENT: if (isTempOrTempToastNamespace(nspid)) newRelation->relpersistence = RELPERSISTENCE_TEMP; diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c index d713d5c..afcf004 100644 --- a/src/backend/catalog/storage.c +++ b/src/backend/catalog/storage.c @@ -27,6 +27,7 @@ #include "access/xlogutils.h" #include "catalog/storage.h" #include "catalog/storage_xlog.h" +#include "catalog/storage_gtt.h" #include "miscadmin.h" #include "storage/freespace.h" #include "storage/smgr.h" @@ -61,6 +62,7 @@ typedef struct PendingRelDelete { RelFileNode relnode; /* relation that may need to be deleted */ BackendId backend; /* InvalidBackendId if not a temp rel */ + Oid relOid; /* InvalidOid if not a global temp rel */ bool atCommit; /* T=delete at commit; F=delete at abort */ int nestLevel; /* xact nesting level of request */ struct PendingRelDelete *next; /* linked-list link */ @@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode) * transaction aborts later on, the storage will be destroyed. */ SMgrRelation -RelationCreateStorage(RelFileNode rnode, char relpersistence) +RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel) { PendingRelDelete *pending; SMgrRelation srel; @@ -127,6 +129,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) switch (relpersistence) { case RELPERSISTENCE_TEMP: + /* global temp table use same storage strategy as local temp table */ + case RELPERSISTENCE_GLOBAL_TEMP: backend = BackendIdForTempRelations(); needs_wal = false; break; @@ -154,6 +158,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); pending->relnode = rnode; pending->backend = backend; + pending->relOid = InvalidOid; pending->atCommit = false; /* delete if abort */ pending->nestLevel = GetCurrentTransactionNestLevel(); pending->next = pendingDeletes; @@ -165,6 +170,13 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) AddPendingSync(&rnode); } + /* remember global temp table storage info to localhash */ + if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel) + { + pending->relOid = RelationGetRelid(rel); + remember_gtt_storage_info(rnode, rel); + } + return srel; } @@ -201,11 +213,15 @@ RelationDropStorage(Relation rel) MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); pending->relnode = rel->rd_node; pending->backend = rel->rd_backend; + pending->relOid = InvalidOid; pending->atCommit = true; /* delete if commit */ pending->nestLevel = GetCurrentTransactionNestLevel(); pending->next = pendingDeletes; pendingDeletes = pending; + if (RELATION_IS_GLOBAL_TEMP(rel)) + pending->relOid = RelationGetRelid(rel); + /* * NOTE: if the relation was created in this transaction, it will now be * present in the pending-delete list twice, once with atCommit true and @@ -602,6 +618,7 @@ smgrDoPendingDeletes(bool isCommit) i = 0, maxrels = 0; SMgrRelation *srels = NULL; + Oid *reloids = NULL; prev = NULL; for (pending = pendingDeletes; pending != NULL; pending = next) @@ -631,14 +648,18 @@ smgrDoPendingDeletes(bool isCommit) { maxrels = 8; srels = palloc(sizeof(SMgrRelation) * maxrels); + reloids = palloc(sizeof(Oid) * maxrels); } else if (maxrels <= nrels) { maxrels *= 2; srels = repalloc(srels, sizeof(SMgrRelation) * maxrels); + reloids = repalloc(reloids, sizeof(Oid) * maxrels); } - srels[nrels++] = srel; + srels[nrels] = srel; + reloids[nrels] = pending->relOid; + nrels++; } /* must explicitly free the list entry */ pfree(pending); @@ -651,9 +672,18 @@ smgrDoPendingDeletes(bool isCommit) smgrdounlinkall(srels, nrels, false); for (i = 0; i < nrels; i++) + { smgrclose(srels[i]); + /* clean global temp table flags when transaction commit or rollback */ + if (SmgrIsTemp(srels[i]) && + reloids[i] != InvalidOid && + gtt_storage_attached(reloids[i])) + forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit); + } + pfree(srels); + pfree(reloids); } } diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c new file mode 100644 index 0000000..451cf82 --- /dev/null +++ b/src/backend/catalog/storage_gtt.c @@ -0,0 +1,1491 @@ +/*------------------------------------------------------------------------- + * + * storage_gtt.c + * code to create and destroy physical storage for global temparary table + * + * IDENTIFICATION + * src/backend/catalog/storage_gtt.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/amapi.h" +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/multixact.h" +#include "access/table.h" +#include "access/relation.h" +#include "access/visibilitymap.h" +#include "access/xact.h" +#include "access/xlog.h" +#include "access/xloginsert.h" +#include "access/xlogutils.h" +#include "catalog/storage.h" +#include "catalog/storage_xlog.h" +#include "catalog/storage_gtt.h" +#include "catalog/heap.h" +#include "catalog/namespace.h" +#include "catalog/index.h" +#include "catalog/pg_type.h" +#include "catalog/pg_statistic.h" +#include "commands/tablecmds.h" +#include "commands/sequence.h" +#include "funcapi.h" +#include "nodes/primnodes.h" +#include "nodes/pg_list.h" +#include "nodes/execnodes.h" +#include "miscadmin.h" +#include "storage/freespace.h" +#include "storage/smgr.h" +#include "storage/ipc.h" +#include "storage/proc.h" +#include "storage/procarray.h" +#include "storage/lwlock.h" +#include "storage/shmem.h" +#include "storage/sinvaladt.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/hsearch.h" +#include "utils/catcache.h" +#include "utils/lsyscache.h" +#include +#include "utils/inval.h" +#include "utils/guc.h" + + +/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */ +#define WORDNUM(x) ((x) / BITS_PER_BITMAPWORD) +#define BITNUM(x) ((x) % BITS_PER_BITMAPWORD) + +#define BITMAPSET_SIZE(nwords) \ + (offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword)) + +static bool gtt_cleaner_exit_registered = false; +static HTAB *gtt_storage_local_hash = NULL; +static HTAB *active_gtt_shared_hash = NULL; +static MemoryContext gtt_relstats_context = NULL; + +/* relfrozenxid of all gtts in the current session */ +static List *gtt_session_relfrozenxid_list = NIL; +static TransactionId gtt_session_frozenxid = InvalidTransactionId; + +int vacuum_gtt_defer_check_age = 0; + +typedef struct gtt_ctl_data +{ + LWLock lock; + int max_entry; + int entry_size; +}gtt_ctl_data; + +static gtt_ctl_data *gtt_shared_ctl = NULL; + +typedef struct gtt_fnode +{ + Oid dbNode; + Oid relNode; +} gtt_fnode; + +typedef struct +{ + gtt_fnode rnode; + Bitmapset *map; + /* bitmap data */ +} gtt_shared_hash_entry; + +typedef struct +{ + Oid relfilenode; + Oid spcnode; + + /* pg_class stat */ + int32 relpages; + float4 reltuples; + int32 relallvisible; + TransactionId relfrozenxid; + TransactionId relminmxid; +} gtt_relfilenode; + +typedef struct +{ + Oid relid; + + List *relfilenode_list; + + char relkind; + bool on_commit_delete; + + /* pg_statistic */ + int natts; + int *attnum; + HeapTuple *att_stat_tups; + + Oid oldrelid; /* remember the source of relid, before the switch relfilenode. */ +} gtt_local_hash_entry; + +static Size action_gtt_shared_hash_entry_size(void); +static void gtt_storage_checkin(Oid relid); +static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit); +static void gtt_storage_removeall(int code, Datum arg); +static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid); +static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid); +static void set_gtt_session_relfrozenxid(void); +static void gtt_reset_statistics(gtt_local_hash_entry *entry); +static void gtt_free_statistics(gtt_local_hash_entry *entry); +static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry *entry, Oid relfilenode, bool missing_ok); +static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok); + +Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS); +Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS); +Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS); +Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS); + + +static Size +action_gtt_shared_hash_entry_size(void) +{ + int wordnum; + Size hash_entry_size = 0; + + if (max_active_gtt <= 0) + return 0; + + wordnum = WORDNUM(MaxBackends + 1); + hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry)); + hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1)); + + return hash_entry_size; +} + +Size +active_gtt_shared_hash_size(void) +{ + Size size = 0; + Size hash_entry_size = 0; + + if (max_active_gtt <= 0) + return 0; + + size = MAXALIGN(sizeof(gtt_ctl_data)); + hash_entry_size = action_gtt_shared_hash_entry_size(); + size += hash_estimate_size(max_active_gtt, hash_entry_size); + + return size; +} + +void +active_gtt_shared_hash_init(void) +{ + HASHCTL info; + bool found; + + if (max_active_gtt <= 0) + return; + + gtt_shared_ctl = + ShmemInitStruct("gtt_shared_ctl", + sizeof(gtt_ctl_data), + &found); + + if (!found) + { + LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl"); + LWLockInitialize(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL); + gtt_shared_ctl->max_entry = max_active_gtt; + gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size(); + } + + MemSet(&info, 0, sizeof(info)); + info.keysize = sizeof(gtt_fnode); + info.entrysize = action_gtt_shared_hash_entry_size(); + active_gtt_shared_hash = + ShmemInitHash("active gtt shared hash", + gtt_shared_ctl->max_entry, + gtt_shared_ctl->max_entry, + &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE); +} + +static void +gtt_storage_checkin(Oid relid) +{ + gtt_shared_hash_entry *entry; + bool found; + gtt_fnode fnode; + + if (max_active_gtt <= 0) + return; + + fnode.dbNode = MyDatabaseId; + fnode.relNode = relid; + LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE); + entry = hash_search(active_gtt_shared_hash, + (void *)&(fnode), HASH_ENTER_NULL, &found); + + if (entry == NULL) + { + LWLockRelease(>t_shared_ctl->lock); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of shared memory"), + errhint("You might need to increase max_active_global_temporary_table."))); + } + + if (found == false) + { + int wordnum; + + entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry))); + wordnum = WORDNUM(MaxBackends + 1); + memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1)); + entry->map->nwords = wordnum + 1; + } + + bms_add_member(entry->map, MyBackendId); + LWLockRelease(>t_shared_ctl->lock); +} + +static void +gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit) +{ + gtt_shared_hash_entry *entry; + gtt_fnode fnode; + + if (max_active_gtt <= 0) + return; + + fnode.dbNode = MyDatabaseId; + fnode.relNode = relid; + if (!skiplock) + LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE); + + entry = hash_search(active_gtt_shared_hash, + (void *) &(fnode), HASH_FIND, NULL); + + if (entry == NULL) + { + if (!skiplock) + LWLockRelease(>t_shared_ctl->lock); + + if (isCommit) + elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid); + + return; + } + + Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends); + bms_del_member(entry->map, MyBackendId); + + if (bms_is_empty(entry->map)) + { + if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL)) + elog(PANIC, "gtt shared hash table corrupted"); + } + + if (!skiplock) + LWLockRelease(>t_shared_ctl->lock); + + return; +} + +Bitmapset * +copy_active_gtt_bitmap(Oid relid) +{ + gtt_shared_hash_entry *entry; + Bitmapset *map_copy = NULL; + gtt_fnode fnode; + + if (max_active_gtt <= 0) + return NULL; + + fnode.dbNode = MyDatabaseId; + fnode.relNode = relid; + LWLockAcquire(>t_shared_ctl->lock, LW_SHARED); + entry = hash_search(active_gtt_shared_hash, + (void *) &(fnode), HASH_FIND, NULL); + + if (entry == NULL) + { + LWLockRelease(>t_shared_ctl->lock); + return NULL; + } + + Assert(entry->map); + if (!bms_is_empty(entry->map)) + map_copy = bms_copy(entry->map); + + LWLockRelease(>t_shared_ctl->lock); + + return map_copy; +} + +bool +is_other_backend_use_gtt(Oid relid) +{ + gtt_shared_hash_entry *entry; + bool in_use = false; + int num_use = 0; + gtt_fnode fnode; + + if (max_active_gtt <= 0) + return false; + + fnode.dbNode = MyDatabaseId; + fnode.relNode = relid; + LWLockAcquire(>t_shared_ctl->lock, LW_SHARED); + entry = hash_search(active_gtt_shared_hash, + (void *) &(fnode), HASH_FIND, NULL); + + if (entry == NULL) + { + LWLockRelease(>t_shared_ctl->lock); + return false; + } + + Assert(entry->map); + Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends); + + num_use = bms_num_members(entry->map); + if (num_use == 0) + in_use = false; + else if (num_use == 1) + { + if(bms_is_member(MyBackendId, entry->map)) + in_use = false; + else + in_use = true; + } + else + in_use = true; + + LWLockRelease(>t_shared_ctl->lock); + + return in_use; +} + +void +remember_gtt_storage_info(RelFileNode rnode, Relation rel) +{ + gtt_local_hash_entry *entry; + MemoryContext oldcontext; + gtt_relfilenode *new_node = NULL; + Oid relid = RelationGetRelid(rel); + + if (max_active_gtt <= 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Global temporary table feature is disable"), + errhint("You might need to increase max_active_global_temporary_table to enable this feature."))); + + if (RecoveryInProgress()) + elog(ERROR, "readonly mode not support access global temporary table"); + + if (rel->rd_rel->relkind == RELKIND_INDEX && + rel->rd_index && + (!rel->rd_index->indisvalid || + !rel->rd_index->indisready || + !rel->rd_index->indislive)) + elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel)); + + if (gtt_storage_local_hash == NULL) + { +#define GTT_LOCAL_HASH_SIZE 1024 + /* First time through: initialize the hash table */ + HASHCTL ctl; + + MemSet(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(gtt_local_hash_entry); + gtt_storage_local_hash = + hash_create("global temporary table info", + GTT_LOCAL_HASH_SIZE, + &ctl, HASH_ELEM | HASH_BLOBS); + + if (!CacheMemoryContext) + CreateCacheMemoryContext(); + + gtt_relstats_context = + AllocSetContextCreate(CacheMemoryContext, + "gtt relstats context", + ALLOCSET_DEFAULT_SIZES); + } + + oldcontext = MemoryContextSwitchTo(gtt_relstats_context); + + entry = gtt_search_by_relid(relid, true); + if (!entry) + { + bool found = false; + int natts = 0; + + /* Look up or create an entry */ + entry = hash_search(gtt_storage_local_hash, + (void *) &relid, HASH_ENTER, &found); + + if (found) + { + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "backend %d relid %u already exists in gtt local hash", + MyBackendId, relid); + } + + entry->relfilenode_list = NIL; + entry->relkind = rel->rd_rel->relkind; + entry->on_commit_delete = false; + entry->natts = 0; + entry->attnum = NULL; + entry->att_stat_tups = NULL; + entry->oldrelid = InvalidOid; + + natts = RelationGetNumberOfAttributes(rel); + entry->attnum = palloc0(sizeof(int) * natts); + entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts); + entry->natts = natts; + + if (entry->relkind == RELKIND_RELATION) + { + if (RELATION_GTT_ON_COMMIT_DELETE(rel)) + { + entry->on_commit_delete = true; + register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS); + } + } + + if (entry->relkind == RELKIND_RELATION || + entry->relkind == RELKIND_SEQUENCE) + { + gtt_storage_checkin(relid); + } + } + + new_node = palloc0(sizeof(gtt_relfilenode)); + new_node->relfilenode = rnode.relNode; + new_node->spcnode = rnode.spcNode; + new_node->relpages = 0; + new_node->reltuples = 0; + new_node->relallvisible = 0; + new_node->relfrozenxid = InvalidTransactionId; + new_node->relminmxid = InvalidMultiXactId; + entry->relfilenode_list = lappend(entry->relfilenode_list, new_node); + + /* only heap contain transaction information */ + if (entry->relkind == RELKIND_RELATION) + { + new_node->relfrozenxid = RecentXmin; + new_node->relminmxid = GetOldestMultiXactId(); + insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid); + set_gtt_session_relfrozenxid(); + } + + gtt_reset_statistics(entry); + + MemoryContextSwitchTo(oldcontext); + + if (!gtt_cleaner_exit_registered) + { + before_shmem_exit(gtt_storage_removeall, 0); + gtt_cleaner_exit_registered = true; + } + + return; +} + +void +forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit) +{ + gtt_local_hash_entry *entry = NULL; + gtt_relfilenode *d_rnode = NULL; + + if (max_active_gtt <= 0) + return; + + entry = gtt_search_by_relid(relid, true); + if (entry == NULL) + { + if (isCommit) + elog(ERROR,"gtt rel %u not found in local hash", relid); + + return; + } + + d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true); + if (d_rnode == NULL) + { + if (isCommit) + elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid); + else if (entry->oldrelid != InvalidOid) + { + gtt_local_hash_entry *entry2 = NULL; + gtt_relfilenode *gttnode2 = NULL; + + entry2 = gtt_search_by_relid(entry->oldrelid, false); + gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false); + Assert(gttnode2->relfilenode == rnode.relNode); + Assert(list_length(entry->relfilenode_list) == 1); + /* rollback switch relfilenode */ + gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode, + entry->relid, gtt_fetch_current_relfilenode(entry->relid), + false); + /* clean up footprint */ + entry2->oldrelid = InvalidOid; + d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false); + Assert(d_rnode); + } + else + { + if (entry->relfilenode_list == NIL) + { + if (entry->relkind == RELKIND_RELATION || + entry->relkind == RELKIND_SEQUENCE) + gtt_storage_checkout(relid, false, isCommit); + + gtt_free_statistics(entry); + hash_search(gtt_storage_local_hash, + (void *) &(relid), HASH_REMOVE, NULL); + } + + return; + } + } + + if (entry->relkind == RELKIND_RELATION) + { + Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit); + if (TransactionIdIsValid(d_rnode->relfrozenxid)) + { + remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid); + set_gtt_session_relfrozenxid(); + } + } + + entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode); + pfree(d_rnode); + if (entry->relfilenode_list == NIL) + { + if (entry->relkind == RELKIND_RELATION || + entry->relkind == RELKIND_SEQUENCE) + gtt_storage_checkout(relid, false, isCommit); + + if (isCommit && entry->oldrelid != InvalidOid) + { + gtt_local_hash_entry *entry2 = NULL; + + entry2 = gtt_search_by_relid(entry->oldrelid, false); + /* clean up footprint */ + entry2->oldrelid = InvalidOid; + } + + gtt_free_statistics(entry); + hash_search(gtt_storage_local_hash, + (void *) &(relid), HASH_REMOVE, NULL); + } + else + gtt_reset_statistics(entry); + + return; +} + +/* is the storage file was created in this backend */ +bool +gtt_storage_attached(Oid relid) +{ + bool found = false; + gtt_local_hash_entry *entry = NULL; + + if (max_active_gtt <= 0) + return false; + + entry = gtt_search_by_relid(relid, true); + if (entry) + found = true; + + return found; +} + +static void +gtt_storage_removeall(int code, Datum arg) +{ + HASH_SEQ_STATUS status; + gtt_local_hash_entry *entry; + SMgrRelation *srels = NULL; + Oid *relids = NULL; + char *relkinds = NULL; + int nrels = 0, + nfiles = 0, + maxrels = 0, + maxfiles = 0, + i = 0; + + if (gtt_storage_local_hash == NULL) + return; + + hash_seq_init(&status, gtt_storage_local_hash); + while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL) + { + ListCell *lc; + + foreach(lc, entry->relfilenode_list) + { + SMgrRelation srel; + RelFileNode rnode; + gtt_relfilenode *gtt_rnode = lfirst(lc); + + rnode.spcNode = gtt_rnode->spcnode; + rnode.dbNode = MyDatabaseId; + rnode.relNode = gtt_rnode->relfilenode; + srel = smgropen(rnode, MyBackendId); + + if (maxfiles == 0) + { + maxfiles = 32; + srels = palloc(sizeof(SMgrRelation) * maxfiles); + } + else if (maxfiles <= nfiles) + { + maxfiles *= 2; + srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles); + } + + srels[nfiles++] = srel; + } + + if (maxrels == 0) + { + maxrels = 32; + relids = palloc(sizeof(Oid) * maxrels); + relkinds = palloc(sizeof(char) * maxrels); + } + else if (maxrels <= nrels) + { + maxrels *= 2; + relids = repalloc(relids , sizeof(Oid) * maxrels); + relkinds = repalloc(relkinds, sizeof(char) * maxrels); + } + + relkinds[nrels] = entry->relkind; + relids[nrels] = entry->relid; + nrels++; + } + + if (nfiles > 0) + { + smgrdounlinkall(srels, nfiles, false); + for (i = 0; i < nfiles; i++) + smgrclose(srels[i]); + + pfree(srels); + } + + if (nrels) + { + LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE); + for (i = 0; i < nrels; i++) + { + if (relkinds[i] == RELKIND_RELATION || + relkinds[i] == RELKIND_SEQUENCE) + gtt_storage_checkout(relids[i], true, false); + } + LWLockRelease(>t_shared_ctl->lock); + + pfree(relids); + pfree(relkinds); + } + + MyProc->session_gtt_frozenxid = InvalidTransactionId; + + return; +} + +/* + * Update global temp table relstats(relpage/reltuple/relallvisible) + * to local hashtable + */ +void +up_gtt_relstats(Relation relation, + BlockNumber num_pages, + double num_tuples, + BlockNumber num_all_visible_pages, + TransactionId relfrozenxid, + TransactionId relminmxid) +{ + Oid relid = RelationGetRelid(relation); + gtt_local_hash_entry *entry; + gtt_relfilenode *gtt_rnode = NULL; + + if (max_active_gtt <= 0) + return; + + entry = gtt_search_by_relid(relid, true); + if (entry == NULL) + return; + + gtt_rnode = lfirst(list_tail(entry->relfilenode_list)); + if (gtt_rnode == NULL) + return; + + if (num_pages >= 0 && + gtt_rnode->relpages != (int32)num_pages) + gtt_rnode->relpages = (int32)num_pages; + + if (num_tuples >= 0 && + gtt_rnode->reltuples != (float4)num_tuples) + gtt_rnode->reltuples = (float4)num_tuples; + + /* only heap contain transaction information and relallvisible */ + if (entry->relkind == RELKIND_RELATION) + { + if (gtt_rnode->relallvisible >= 0 && + gtt_rnode->relallvisible != (int32)num_all_visible_pages) + { + gtt_rnode->relallvisible = (int32)num_all_visible_pages; + } + + if (TransactionIdIsNormal(relfrozenxid) && + gtt_rnode->relfrozenxid != relfrozenxid && + (TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) || + TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid))) + { + remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid); + gtt_rnode->relfrozenxid = relfrozenxid; + insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid); + set_gtt_session_relfrozenxid(); + } + + if (MultiXactIdIsValid(relminmxid) && + gtt_rnode->relminmxid != relminmxid && + (MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) || + MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid))) + { + gtt_rnode->relminmxid = relminmxid; + } + } + + return; +} + +/* + * Search global temp table relstats(relpage/reltuple/relallvisible) + * from local hashtable. + */ +bool +get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples, + BlockNumber *relallvisible, TransactionId *relfrozenxid, + TransactionId *relminmxid) +{ + gtt_local_hash_entry *entry; + gtt_relfilenode *gtt_rnode = NULL; + + if (max_active_gtt <= 0) + return false; + + entry = gtt_search_by_relid(relid, true); + if (entry == NULL) + return false; + + Assert(entry->relid == relid); + + gtt_rnode = lfirst(list_tail(entry->relfilenode_list)); + if (gtt_rnode == NULL) + return false; + + if (relpages) + *relpages = gtt_rnode->relpages; + + if (reltuples) + *reltuples = gtt_rnode->reltuples; + + if (relallvisible) + *relallvisible = gtt_rnode->relallvisible; + + if (relfrozenxid) + *relfrozenxid = gtt_rnode->relfrozenxid; + + if (relminmxid) + *relminmxid = gtt_rnode->relminmxid; + + return true; +} + +/* + * Update global temp table statistic info(definition is same as pg_statistic) + * to local hashtable where ananyze global temp table + */ +void +up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts, + TupleDesc tupleDescriptor, Datum *values, bool *isnull) +{ + gtt_local_hash_entry *entry; + MemoryContext oldcontext; + int i = 0; + + if (max_active_gtt <= 0) + return; + + entry = gtt_search_by_relid(reloid, true); + if (entry == NULL) + return; + + if (entry->natts < natts) + { + elog(WARNING, "reloid %u not support update attstat after add colunm", reloid); + return; + } + + oldcontext = MemoryContextSwitchTo(gtt_relstats_context); + Assert(entry->relid == reloid); + for (i = 0; i < entry->natts; i++) + { + if (entry->attnum[i] == 0) + { + entry->attnum[i] = attnum; + break; + } + else if (entry->attnum[i] == attnum) + { + Assert(entry->att_stat_tups[i]); + heap_freetuple(entry->att_stat_tups[i]); + entry->att_stat_tups[i] = NULL; + break; + } + } + + Assert(i < entry->natts); + Assert(entry->att_stat_tups[i] == NULL); + entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull); + MemoryContextSwitchTo(oldcontext); + + return; +} + +/* + * Search global temp table statistic info(definition is same as pg_statistic) + * from local hashtable. + */ +HeapTuple +get_gtt_att_statistic(Oid reloid, int attnum, bool inh) +{ + gtt_local_hash_entry *entry; + int i = 0; + + if (max_active_gtt <= 0) + return NULL; + + entry = gtt_search_by_relid(reloid, true); + if (entry == NULL) + return NULL; + + for (i = 0; i < entry->natts; i++) + { + if (entry->attnum[i] == attnum) + { + Assert(entry->att_stat_tups[i]); + return entry->att_stat_tups[i]; + } + } + + return NULL; +} + +void +release_gtt_statistic_cache(HeapTuple tup) +{ + /* do nothing */ + return; +} + +static void +insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid) +{ + MemoryContext oldcontext; + ListCell *cell; + int i; + + Assert(TransactionIdIsNormal(relfrozenxid)); + + oldcontext = MemoryContextSwitchTo(gtt_relstats_context); + /* Does the datum belong at the front? */ + if (gtt_session_relfrozenxid_list == NIL || + TransactionIdFollowsOrEquals(relfrozenxid, + linitial_oid(gtt_session_relfrozenxid_list))) + { + gtt_session_relfrozenxid_list = + lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list); + MemoryContextSwitchTo(oldcontext); + + return; + } + + /* No, so find the entry it belongs after */ + i = 0; + foreach (cell, gtt_session_relfrozenxid_list) + { + if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell))) + break; + + i++; + } + gtt_session_relfrozenxid_list = + list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid); + MemoryContextSwitchTo(oldcontext); + + return; +} + +static void +remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid) +{ + gtt_session_relfrozenxid_list = + list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid); +} + +static void +set_gtt_session_relfrozenxid(void) +{ + TransactionId gtt_frozenxid = InvalidTransactionId; + + if (gtt_session_relfrozenxid_list) + gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list); + + gtt_session_frozenxid = gtt_frozenxid; + if (MyProc->session_gtt_frozenxid != gtt_frozenxid) + MyProc->session_gtt_frozenxid = gtt_frozenxid; +} + +Datum +pg_get_gtt_statistics(PG_FUNCTION_ARGS) +{ + HeapTuple tuple; + Relation rel = NULL; + int attnum = PG_GETARG_INT32(1); + Oid reloid = PG_GETARG_OID(0); + char rel_persistence; + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + MemoryContext oldcontext; + Tuplestorestate *tupstore; + Relation pg_tatistic = NULL; + TupleDesc sd; + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not " \ + "allowed in this context"))); + + oldcontext = MemoryContextSwitchTo( + rsinfo->econtext->ecxt_per_query_memory); + + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + MemoryContextSwitchTo(oldcontext); + + rel = relation_open(reloid, AccessShareLock); + rel_persistence = get_rel_persistence(reloid); + if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP) + { + elog(WARNING, "relation OID %u is not a global temporary table", reloid); + relation_close(rel, NoLock); + return (Datum) 0; + } + + pg_tatistic = relation_open(StatisticRelationId, AccessShareLock); + sd = RelationGetDescr(pg_tatistic); + + tuple = get_gtt_att_statistic(reloid, attnum, false); + if (tuple) + { + Datum values[31]; + bool isnull[31]; + HeapTuple res = NULL; + + memset(&values, 0, sizeof(values)); + memset(&isnull, 0, sizeof(isnull)); + heap_deform_tuple(tuple, sd, values, isnull); + res = heap_form_tuple(tupdesc, values, isnull); + tuplestore_puttuple(tupstore, res); + } + + relation_close(rel, NoLock); + relation_close(pg_tatistic, AccessShareLock); + tuplestore_donestoring(tupstore); + + return (Datum) 0; +} + +Datum +pg_get_gtt_relstats(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + MemoryContext oldcontext; + HeapTuple tuple; + Oid reloid = PG_GETARG_OID(0); + char rel_persistence; + BlockNumber relpages = 0; + double reltuples = 0; + BlockNumber relallvisible = 0; + uint32 relfrozenxid = 0; + uint32 relminmxid = 0; + Oid relnode = 0; + Relation rel = NULL; + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + oldcontext = MemoryContextSwitchTo( + rsinfo->econtext->ecxt_per_query_memory); + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + MemoryContextSwitchTo(oldcontext); + + rel = relation_open(reloid, AccessShareLock); + rel_persistence = get_rel_persistence(reloid); + if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP) + { + elog(WARNING, "relation OID %u is not a global temporary table", reloid); + relation_close(rel, NoLock); + return (Datum) 0; + } + + get_gtt_relstats(reloid, + &relpages, &reltuples, &relallvisible, + &relfrozenxid, &relminmxid); + relnode = gtt_fetch_current_relfilenode(reloid); + if (relnode != InvalidOid) + { + Datum values[6]; + bool isnull[6]; + + memset(isnull, 0, sizeof(isnull)); + memset(values, 0, sizeof(values)); + values[0] = UInt32GetDatum(relnode); + values[1] = Int32GetDatum(relpages); + values[2] = Float4GetDatum((float4)reltuples); + values[3] = Int32GetDatum(relallvisible); + values[4] = UInt32GetDatum(relfrozenxid); + values[5] = UInt32GetDatum(relminmxid); + tuple = heap_form_tuple(tupdesc, values, isnull); + tuplestore_puttuple(tupstore, tuple); + } + + tuplestore_donestoring(tupstore); + relation_close(rel, NoLock); + + return (Datum) 0; +} + +Datum +pg_gtt_attached_pid(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + MemoryContext oldcontext; + HeapTuple tuple; + Oid reloid = PG_GETARG_OID(0); + char rel_persistence; + Relation rel = NULL; + PGPROC *proc = NULL; + Bitmapset *map = NULL; + pid_t pid = 0; + int backendid = 0; + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + oldcontext = MemoryContextSwitchTo( + rsinfo->econtext->ecxt_per_query_memory); + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + MemoryContextSwitchTo(oldcontext); + + rel = relation_open(reloid, AccessShareLock); + rel_persistence = get_rel_persistence(reloid); + if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP) + { + elog(WARNING, "relation OID %u is not a global temporary table", reloid); + relation_close(rel, NoLock); + return (Datum) 0; + } + + map = copy_active_gtt_bitmap(reloid); + if (map) + { + backendid = bms_first_member(map); + + do + { + proc = BackendIdGetProc(backendid); + pid = proc->pid; + if (pid > 0) + { + Datum values[2]; + bool isnull[2]; + + memset(isnull, false, sizeof(isnull)); + memset(values, 0, sizeof(values)); + values[0] = UInt32GetDatum(reloid); + values[1] = Int32GetDatum(pid); + tuple = heap_form_tuple(tupdesc, values, isnull); + tuplestore_puttuple(tupstore, tuple); + } + backendid = bms_next_member(map, backendid); + } while (backendid > 0); + + pfree(map); + } + + tuplestore_donestoring(tupstore); + relation_close(rel, NoLock); + + return (Datum) 0; +} + +Datum +pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + MemoryContext oldcontext; + HeapTuple tuple; + int num_xid = MaxBackends + 1; + int *pids = NULL; + uint32 *xids = NULL; + int i = 0; + int j = 0; + uint32 oldest = 0; + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + oldcontext = MemoryContextSwitchTo( + rsinfo->econtext->ecxt_per_query_memory); + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + MemoryContextSwitchTo(oldcontext); + + if (max_active_gtt <= 0) + return (Datum) 0; + + if (RecoveryInProgress()) + return (Datum) 0; + + pids = palloc0(sizeof(int) * num_xid); + xids = palloc0(sizeof(int) * num_xid); + oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i); + if (i > 0) + { + if (i > 0) + { + pids[i] = 0; + xids[i] = oldest; + i++; + } + + for(j = 0; j < i; j++) + { + Datum values[2]; + bool isnull[2]; + + memset(isnull, false, sizeof(isnull)); + memset(values, 0, sizeof(values)); + values[0] = Int32GetDatum(pids[j]); + values[1] = UInt32GetDatum(xids[j]); + tuple = heap_form_tuple(tupdesc, values, isnull); + tuplestore_puttuple(tupstore, tuple); + } + } + tuplestore_donestoring(tupstore); + pfree(pids); + pfree(xids); + + return (Datum) 0; +} + +void +gtt_force_enable_index(Relation index) +{ + if (!RELATION_IS_GLOBAL_TEMP(index)) + return; + + Assert(index->rd_rel->relkind == RELKIND_INDEX); + Assert(OidIsValid(RelationGetRelid(index))); + + index->rd_index->indisvalid = true; + index->rd_index->indislive = true; + index->rd_index->indisready = true; +} + +void +gtt_fix_index_state(Relation index) +{ + Oid indexOid = RelationGetRelid(index); + Oid relOid = index->rd_index->indrelid; + + if (!RELATION_IS_GLOBAL_TEMP(index)) + return; + + if (!index->rd_index->indisvalid) + return; + + if (gtt_storage_attached(relOid) && + !gtt_storage_attached(indexOid)) + { + index->rd_index->indisvalid = false; + index->rd_index->indislive = false; + index->rd_index->indisready = false; + } + + return; +} + +void +init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo) +{ + Relation relation = resultRelInfo->ri_RelationDesc; + int i; + Oid toastrelid; + + if (!(operation == CMD_UTILITY || operation == CMD_INSERT)) + return; + + if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind)) + return; + + if (!RELATION_IS_GLOBAL_TEMP(relation)) + return; + + if (gtt_storage_attached(RelationGetRelid(relation))) + return; + + RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation); + + for (i = 0; i < resultRelInfo->ri_NumIndices; i++) + { + Relation index = resultRelInfo->ri_IndexRelationDescs[i]; + IndexInfo *info = resultRelInfo->ri_IndexRelationInfo[i]; + + Assert(index->rd_index->indisvalid); + Assert(index->rd_index->indislive); + Assert(index->rd_index->indisready); + + index_build(relation, index, info, true, false); + } + + toastrelid = relation->rd_rel->reltoastrelid; + if (OidIsValid(toastrelid)) + { + Relation toastrel; + ListCell *indlist; + + toastrel = table_open(toastrelid, RowExclusiveLock); + RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel); + + foreach(indlist, RelationGetIndexList(toastrel)) + { + Oid indexId = lfirst_oid(indlist); + Relation currentIndex; + IndexInfo *indexInfo; + + currentIndex = index_open(indexId, RowExclusiveLock); + + indexInfo = BuildDummyIndexInfo(currentIndex); + index_build(toastrel, currentIndex, indexInfo, true, false); + index_close(currentIndex, NoLock); + } + + table_close(toastrel, NoLock); + } + + return; +} + +static void +gtt_reset_statistics(gtt_local_hash_entry *entry) +{ + int i; + + for (i = 0; i < entry->natts; i++) + { + if (entry->att_stat_tups[i]) + { + heap_freetuple(entry->att_stat_tups[i]); + entry->att_stat_tups[i] = NULL; + } + + entry->attnum[i] = 0; + } + + return; +} + +static void +gtt_free_statistics(gtt_local_hash_entry *entry) +{ + int i; + + for (i = 0; i < entry->natts; i++) + { + if (entry->att_stat_tups[i]) + { + heap_freetuple(entry->att_stat_tups[i]); + entry->att_stat_tups[i] = NULL; + } + } + + if (entry->attnum) + pfree(entry->attnum); + + if (entry->att_stat_tups) + pfree(entry->att_stat_tups); + + return; +} + +Oid +gtt_fetch_current_relfilenode(Oid relid) +{ + gtt_local_hash_entry *entry; + gtt_relfilenode *gtt_rnode = NULL; + + if (max_active_gtt <= 0) + return InvalidOid; + + entry = gtt_search_by_relid(relid, true); + if (entry == NULL) + return InvalidOid; + + Assert(entry->relid == relid); + + gtt_rnode = lfirst(list_tail(entry->relfilenode_list)); + if (gtt_rnode == NULL) + return InvalidOid; + + return gtt_rnode->relfilenode; +} + +void +gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint) +{ + gtt_local_hash_entry *entry1; + gtt_local_hash_entry *entry2; + gtt_relfilenode *gtt_rnode1 = NULL; + gtt_relfilenode *gtt_rnode2 = NULL; + MemoryContext oldcontext; + + if (max_active_gtt <= 0) + return; + + if (gtt_storage_local_hash == NULL) + return; + + entry1 = gtt_search_by_relid(rel1, false); + gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false); + + entry2 = gtt_search_by_relid(rel2, false); + gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false); + + oldcontext = MemoryContextSwitchTo(gtt_relstats_context); + entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1); + entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1); + + entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2); + entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2); + MemoryContextSwitchTo(oldcontext); + + if (footprint) + { + entry1->oldrelid = rel2; + entry2->oldrelid = rel1; + } + + return; +} + +static gtt_relfilenode * +gtt_search_relfilenode(gtt_local_hash_entry *entry, Oid relfilenode, bool missing_ok) +{ + gtt_relfilenode *rnode = NULL; + ListCell *lc; + + Assert(entry); + + foreach(lc, entry->relfilenode_list) + { + gtt_relfilenode *gtt_rnode = lfirst(lc); + if (gtt_rnode->relfilenode == relfilenode) + { + rnode = gtt_rnode; + break; + } + } + + if (!missing_ok && rnode == NULL) + elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid); + + return rnode; +} + +static gtt_local_hash_entry * +gtt_search_by_relid(Oid relid, bool missing_ok) +{ + gtt_local_hash_entry *entry = NULL; + + if (gtt_storage_local_hash == NULL) + return NULL; + + entry = hash_search(gtt_storage_local_hash, + (void *) &(relid), HASH_FIND, NULL); + + if (entry == NULL && !missing_ok) + elog(ERROR, "relid %u not found in local hash", relid); + + return entry; +} + diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 2bd5f5e..1a94c28 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS WHERE NOT pg_is_other_temp_schema(N.oid) AND relkind = 'S'; +-- For global temporary table +CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS + SELECT n.nspname AS schemaname, + c.relname AS tablename, + s.* + FROM + pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace, + pg_get_gtt_relstats(c.oid) as s + WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid)); + +CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS + SELECT n.nspname AS schemaname, + c.relname AS tablename, + s.* + FROM + pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace, + pg_gtt_attached_pid(c.oid) as s + WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid)); + +CREATE VIEW pg_gtt_stats WITH (security_barrier) AS +SELECT n.nspname AS schemaname, + c.relname AS tablename, + a.attname, + s.stainherit AS inherited, + s.stanullfrac AS null_frac, + s.stawidth AS avg_width, + s.stadistinct AS n_distinct, + CASE + WHEN s.stakind1 = 1 THEN s.stavalues1 + WHEN s.stakind2 = 1 THEN s.stavalues2 + WHEN s.stakind3 = 1 THEN s.stavalues3 + WHEN s.stakind4 = 1 THEN s.stavalues4 + WHEN s.stakind5 = 1 THEN s.stavalues5 + END AS most_common_vals, + CASE + WHEN s.stakind1 = 1 THEN s.stanumbers1 + WHEN s.stakind2 = 1 THEN s.stanumbers2 + WHEN s.stakind3 = 1 THEN s.stanumbers3 + WHEN s.stakind4 = 1 THEN s.stanumbers4 + WHEN s.stakind5 = 1 THEN s.stanumbers5 + END AS most_common_freqs, + CASE + WHEN s.stakind1 = 2 THEN s.stavalues1 + WHEN s.stakind2 = 2 THEN s.stavalues2 + WHEN s.stakind3 = 2 THEN s.stavalues3 + WHEN s.stakind4 = 2 THEN s.stavalues4 + WHEN s.stakind5 = 2 THEN s.stavalues5 + END AS histogram_bounds, + CASE + WHEN s.stakind1 = 3 THEN s.stanumbers1[1] + WHEN s.stakind2 = 3 THEN s.stanumbers2[1] + WHEN s.stakind3 = 3 THEN s.stanumbers3[1] + WHEN s.stakind4 = 3 THEN s.stanumbers4[1] + WHEN s.stakind5 = 3 THEN s.stanumbers5[1] + END AS correlation, + CASE + WHEN s.stakind1 = 4 THEN s.stavalues1 + WHEN s.stakind2 = 4 THEN s.stavalues2 + WHEN s.stakind3 = 4 THEN s.stavalues3 + WHEN s.stakind4 = 4 THEN s.stavalues4 + WHEN s.stakind5 = 4 THEN s.stavalues5 + END AS most_common_elems, + CASE + WHEN s.stakind1 = 4 THEN s.stanumbers1 + WHEN s.stakind2 = 4 THEN s.stanumbers2 + WHEN s.stakind3 = 4 THEN s.stanumbers3 + WHEN s.stakind4 = 4 THEN s.stanumbers4 + WHEN s.stakind5 = 4 THEN s.stanumbers5 + END AS most_common_elem_freqs, + CASE + WHEN s.stakind1 = 5 THEN s.stanumbers1 + WHEN s.stakind2 = 5 THEN s.stanumbers2 + WHEN s.stakind3 = 5 THEN s.stanumbers3 + WHEN s.stakind4 = 5 THEN s.stanumbers4 + WHEN s.stakind5 = 5 THEN s.stanumbers5 + END AS elem_count_histogram + FROM + pg_class c + JOIN pg_attribute a ON c.oid = a.attrelid + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace, + pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s + WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid)); + CREATE VIEW pg_stats WITH (security_barrier) AS SELECT nspname AS schemaname, diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index 924ef37..db12eef 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -34,6 +34,7 @@ #include "catalog/pg_inherits.h" #include "catalog/pg_namespace.h" #include "catalog/pg_statistic_ext.h" +#include "catalog/storage_gtt.h" #include "commands/dbcommands.h" #include "commands/progress.h" #include "commands/tablecmds.h" @@ -103,7 +104,7 @@ static int acquire_inherited_sample_rows(Relation onerel, int elevel, HeapTuple *rows, int targrows, double *totalrows, double *totaldeadrows); static void update_attstats(Oid relid, bool inh, - int natts, VacAttrStats **vacattrstats); + int natts, VacAttrStats **vacattrstats, char relpersistence); static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull); static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull); @@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation, return; } + if (RELATION_IS_GLOBAL_TEMP(onerel) && + !gtt_storage_attached(RelationGetRelid(onerel))) + { + relation_close(onerel, ShareUpdateExclusiveLock); + return; + } + /* * We can ANALYZE any table except pg_statistic. See update_attstats */ @@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params, * pg_statistic for columns we didn't process, we leave them alone.) */ update_attstats(RelationGetRelid(onerel), inh, - attr_cnt, vacattrstats); + attr_cnt, vacattrstats, RelationGetRelPersistence(onerel)); for (ind = 0; ind < nindexes; ind++) { AnlIndexData *thisdata = &indexdata[ind]; update_attstats(RelationGetRelid(Irel[ind]), false, - thisdata->attr_cnt, thisdata->vacattrstats); + thisdata->attr_cnt, thisdata->vacattrstats, + RelationGetRelPersistence(Irel[ind])); } /* @@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel, * by taking a self-exclusive lock on the relation in analyze_rel(). */ static void -update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats) +update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence) { Relation sd; int attno; @@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats) } } - /* Is there already a pg_statistic tuple for this attribute? */ - oldtup = SearchSysCache3(STATRELATTINH, - ObjectIdGetDatum(relid), - Int16GetDatum(stats->attr->attnum), - BoolGetDatum(inh)); - - if (HeapTupleIsValid(oldtup)) + /* Update column statistic to localhash, not catalog */ + if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP) { - /* Yes, replace it */ - stup = heap_modify_tuple(oldtup, - RelationGetDescr(sd), - values, - nulls, - replaces); - ReleaseSysCache(oldtup); - CatalogTupleUpdate(sd, &stup->t_self, stup); + up_gtt_att_statistic(relid, + stats->attr->attnum, + inh, + natts, + RelationGetDescr(sd), + values, + nulls); } else { - /* No, insert new tuple */ - stup = heap_form_tuple(RelationGetDescr(sd), values, nulls); - CatalogTupleInsert(sd, stup); - } + /* Is there already a pg_statistic tuple for this attribute? */ + oldtup = SearchSysCache3(STATRELATTINH, + ObjectIdGetDatum(relid), + Int16GetDatum(stats->attr->attnum), + BoolGetDatum(inh)); - heap_freetuple(stup); + if (HeapTupleIsValid(oldtup)) + { + /* Yes, replace it */ + stup = heap_modify_tuple(oldtup, + RelationGetDescr(sd), + values, + nulls, + replaces); + ReleaseSysCache(oldtup); + CatalogTupleUpdate(sd, &stup->t_self, stup); + } + else + { + /* No, insert new tuple */ + stup = heap_form_tuple(RelationGetDescr(sd), values, nulls); + CatalogTupleInsert(sd, stup); + } + + heap_freetuple(stup); + } } table_close(sd, RowExclusiveLock); diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 04d12a7..42c82c8 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -33,6 +33,7 @@ #include "catalog/namespace.h" #include "catalog/objectaccess.h" #include "catalog/pg_am.h" +#include "catalog/storage_gtt.h" #include "catalog/toasting.h" #include "commands/cluster.h" #include "commands/progress.h" @@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose, bool *pSwapToastByContent, TransactionId *pFreezeXid, MultiXactId *pCutoffMulti); static List *get_tables_to_cluster(MemoryContext cluster_context); +static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, + bool swap_toast_by_content, + bool is_internal, + TransactionId frozenXid, + MultiXactId cutoffMulti, + Oid *mapped_tables); /*--------------------------------------------------------------------------- @@ -366,6 +373,14 @@ cluster_rel(Oid tableOid, Oid indexOid, int options) errmsg("cannot vacuum temporary tables of other sessions"))); } + if (RELATION_IS_GLOBAL_TEMP(OldHeap) && + !gtt_storage_attached(RelationGetRelid(OldHeap))) + { + relation_close(OldHeap, AccessExclusiveLock); + pgstat_progress_end_command(); + return; + } + /* * Also check for active uses of the relation in the current transaction, * including open scans and pending AFTER trigger events. @@ -750,6 +765,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose, BlockNumber num_pages; int elevel = verbose ? INFO : DEBUG2; PGRUsage ru0; + bool is_gtt = false; + uint32 gtt_relfrozenxid = 0; + uint32 gtt_relminmxid = 0; pg_rusage_init(&ru0); @@ -763,6 +781,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose, else OldIndex = NULL; + if (RELATION_IS_GLOBAL_TEMP(OldHeap)) + is_gtt = true; + /* * Their tuple descriptors should be exactly alike, but here we only need * assume that they have the same number of columns. @@ -830,20 +851,37 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose, &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff, NULL); - /* - * FreezeXid will become the table's new relfrozenxid, and that mustn't go - * backwards, so take the max. - */ - if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) && - TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid)) - FreezeXid = OldHeap->rd_rel->relfrozenxid; + if (is_gtt) + { + get_gtt_relstats(OIDOldHeap, + NULL, NULL, NULL, + >t_relfrozenxid, >t_relminmxid); - /* - * MultiXactCutoff, similarly, shouldn't go backwards either. - */ - if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) && - MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid)) - MultiXactCutoff = OldHeap->rd_rel->relminmxid; + if (TransactionIdIsValid(gtt_relfrozenxid) && + TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid)) + FreezeXid = gtt_relfrozenxid; + + if (MultiXactIdIsValid(gtt_relminmxid) && + MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid)) + MultiXactCutoff = gtt_relminmxid; + } + else + { + /* + * FreezeXid will become the table's new relfrozenxid, and that mustn't go + * backwards, so take the max. + */ + if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) && + TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid)) + FreezeXid = OldHeap->rd_rel->relfrozenxid; + + /* + * MultiXactCutoff, similarly, shouldn't go backwards either. + */ + if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) && + MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid)) + MultiXactCutoff = OldHeap->rd_rel->relminmxid; + } /* * Decide whether to use an indexscan or seqscan-and-optional-sort to scan @@ -911,6 +949,12 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose, table_close(OldHeap, NoLock); table_close(NewHeap, NoLock); + if (is_gtt) + { + CommandCounterIncrement(); + return; + } + /* Update pg_class to reflect the correct values of pages and tuples. */ relRelation = table_open(RelationRelationId, RowExclusiveLock); @@ -1346,10 +1390,21 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, * Swap the contents of the heap relations (including any toast tables). * Also set old heap's relfrozenxid to frozenXid. */ - swap_relation_files(OIDOldHeap, OIDNewHeap, + if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP) + { + Assert(!is_system_catalog); + gtt_swap_relation_files(OIDOldHeap, OIDNewHeap, + (OIDOldHeap == RelationRelationId), + swap_toast_by_content, is_internal, + frozenXid, cutoffMulti, mapped_tables); + } + else + { + swap_relation_files(OIDOldHeap, OIDNewHeap, (OIDOldHeap == RelationRelationId), swap_toast_by_content, is_internal, frozenXid, cutoffMulti, mapped_tables); + } /* * If it's a system catalog, queue a sinval message to flush all catcaches @@ -1557,3 +1612,141 @@ get_tables_to_cluster(MemoryContext cluster_context) return rvs; } + +static void +gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, + bool swap_toast_by_content, + bool is_internal, + TransactionId frozenXid, + MultiXactId cutoffMulti, + Oid *mapped_tables) +{ + Relation relRelation; + Oid relfilenode1, + relfilenode2; + Relation rel1; + Relation rel2; + + relRelation = table_open(RelationRelationId, RowExclusiveLock); + + rel1 = relation_open(r1, AccessExclusiveLock); + rel2 = relation_open(r2, AccessExclusiveLock); + + relfilenode1 = gtt_fetch_current_relfilenode(r1); + relfilenode2 = gtt_fetch_current_relfilenode(r2); + + Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2)); + gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true); + + CacheInvalidateRelcache(rel1); + CacheInvalidateRelcache(rel2); + + InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0, + InvalidOid, is_internal); + InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0, + InvalidOid, true); + + if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid) + { + if (swap_toast_by_content) + { + if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid) + { + gtt_swap_relation_files(rel1->rd_rel->reltoastrelid, + rel2->rd_rel->reltoastrelid, + target_is_pg_class, + swap_toast_by_content, + is_internal, + frozenXid, + cutoffMulti, + mapped_tables); + } + else + elog(ERROR, "cannot swap toast files by content when there's only one"); + } + else + { + ObjectAddress baseobject, + toastobject; + long count; + + if (IsSystemRelation(rel1)) + elog(ERROR, "cannot swap toast files by links for system catalogs"); + + if (rel1->rd_rel->reltoastrelid) + { + count = deleteDependencyRecordsFor(RelationRelationId, + rel1->rd_rel->reltoastrelid, + false); + if (count != 1) + elog(ERROR, "expected one dependency record for TOAST table, found %ld", + count); + } + if (rel2->rd_rel->reltoastrelid) + { + count = deleteDependencyRecordsFor(RelationRelationId, + rel2->rd_rel->reltoastrelid, + false); + if (count != 1) + elog(ERROR, "expected one dependency record for TOAST table, found %ld", + count); + } + + /* Register new dependencies */ + baseobject.classId = RelationRelationId; + baseobject.objectSubId = 0; + toastobject.classId = RelationRelationId; + toastobject.objectSubId = 0; + + if (rel1->rd_rel->reltoastrelid) + { + baseobject.objectId = r1; + toastobject.objectId = rel1->rd_rel->reltoastrelid; + recordDependencyOn(&toastobject, &baseobject, + DEPENDENCY_INTERNAL); + } + + if (rel2->rd_rel->reltoastrelid) + { + baseobject.objectId = r2; + toastobject.objectId = rel2->rd_rel->reltoastrelid; + recordDependencyOn(&toastobject, &baseobject, + DEPENDENCY_INTERNAL); + } + } + } + + if (swap_toast_by_content && + rel1->rd_rel->relkind == RELKIND_TOASTVALUE && + rel2->rd_rel->relkind == RELKIND_TOASTVALUE) + { + Oid toastIndex1, + toastIndex2; + + /* Get valid index for each relation */ + toastIndex1 = toast_get_valid_index(r1, + AccessExclusiveLock); + toastIndex2 = toast_get_valid_index(r2, + AccessExclusiveLock); + + gtt_swap_relation_files(toastIndex1, + toastIndex2, + target_is_pg_class, + swap_toast_by_content, + is_internal, + InvalidTransactionId, + InvalidMultiXactId, + mapped_tables); + } + + relation_close(rel1, NoLock); + relation_close(rel2, NoLock); + + table_close(relRelation, RowExclusiveLock); + + RelationCloseSmgrByOid(r1); + RelationCloseSmgrByOid(r2); + + CommandCounterIncrement(); +} + diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index ac07f75..655184d 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -27,6 +27,7 @@ #include "catalog/dependency.h" #include "catalog/pg_authid.h" #include "catalog/pg_type.h" +#include "catalog/storage_gtt.h" #include "commands/copy.h" #include "commands/defrem.h" #include "commands/trigger.h" @@ -1063,7 +1064,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, Assert(rel); /* check read-only transaction and parallel mode */ - if (XactReadOnly && !rel->rd_islocaltemp) + if (XactReadOnly && !RELATION_IS_TEMP(rel)) PreventCommandIfReadOnly("COPY FROM"); cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program, @@ -2794,6 +2795,8 @@ CopyFrom(CopyState cstate) ExecOpenIndices(resultRelInfo, false); + init_gtt_storage(CMD_INSERT, resultRelInfo); + estate->es_result_relations = resultRelInfo; estate->es_num_result_relations = 1; estate->es_result_relation_info = resultRelInfo; diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 2baca12..5338db9 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -521,6 +521,7 @@ DefineIndex(Oid relationId, Snapshot snapshot; int save_nestlevel = -1; int i; + char rel_persistence; /* * Some callers need us to run with an empty default_tablespace; this is a @@ -542,7 +543,9 @@ DefineIndex(Oid relationId, * is more efficient. Do this before any use of the concurrent option is * done. */ - if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP) + rel_persistence = get_rel_persistence(relationId); + if (stmt->concurrent && + !(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)) concurrent = true; else concurrent = false; @@ -2460,7 +2463,8 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent) persistence = irel->rd_rel->relpersistence; index_close(irel, NoLock); - if (concurrent && persistence != RELPERSISTENCE_TEMP) + if (concurrent && + !(persistence == RELPERSISTENCE_TEMP || persistence == RELPERSISTENCE_GLOBAL_TEMP)) ReindexRelationConcurrently(indOid, options); else reindex_index(indOid, false, persistence, @@ -2546,6 +2550,7 @@ ReindexTable(RangeVar *relation, int options, bool concurrent) { Oid heapOid; bool result; + char rel_persistence; /* * The lock level used here should match reindex_relation(). @@ -2560,7 +2565,9 @@ ReindexTable(RangeVar *relation, int options, bool concurrent) 0, RangeVarCallbackOwnsTable, NULL); - if (concurrent && get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP) + rel_persistence = get_rel_persistence(heapOid); + if (concurrent && + !(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)) { result = ReindexRelationConcurrently(heapOid, options); @@ -2761,12 +2768,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind, foreach(l, relids) { Oid relid = lfirst_oid(l); + char rel_persistence; StartTransactionCommand(); /* functions in indexes may want a snapshot set */ PushActiveSnapshot(GetTransactionSnapshot()); - if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP) + rel_persistence = get_rel_persistence(relid); + if (concurrent && + !(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)) { (void) ReindexRelationConcurrently(relid, options); /* ReindexRelationConcurrently() does the verbose output */ diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 6aab73b..234ffa6 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -30,6 +30,8 @@ #include "catalog/objectaccess.h" #include "catalog/pg_sequence.h" #include "catalog/pg_type.h" +#include "catalog/storage.h" +#include "catalog/storage_gtt.h" #include "commands/defrem.h" #include "commands/sequence.h" #include "commands/tablecmds.h" @@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */ */ static SeqTableData *last_used_seq = NULL; -static void fill_seq_with_data(Relation rel, HeapTuple tuple); +static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf); static Relation lock_and_open_sequence(SeqTable seq); static void create_seq_hashtable(void); static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel); @@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity, List **owned_by); static void do_setval(Oid relid, int64 next, bool iscalled); static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity); +int64 get_seqence_start_value(Oid seqid); /* @@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) /* now initialize the sequence's data */ tuple = heap_form_tuple(tupDesc, value, null); - fill_seq_with_data(rel, tuple); + fill_seq_with_data(rel, tuple, InvalidBuffer); /* process OWNED BY if given */ if (owned_by) @@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid) /* * Insert the modified tuple into the new storage file. */ - fill_seq_with_data(seq_rel, tuple); + fill_seq_with_data(seq_rel, tuple, InvalidBuffer); /* Clear local cache so that we don't think we have cached numbers */ /* Note that we do not change the currval() state */ @@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid) * Initialize a sequence's relation with the specified tuple as content */ static void -fill_seq_with_data(Relation rel, HeapTuple tuple) +fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf) { - Buffer buf; Page page; sequence_magic *sm; OffsetNumber offnum; + bool lockBuffer = false; /* Initialize first page of relation with special magic number */ - buf = ReadBuffer(rel, P_NEW); - Assert(BufferGetBlockNumber(buf) == 0); + if (buf == InvalidBuffer) + { + buf = ReadBuffer(rel, P_NEW); + Assert(BufferGetBlockNumber(buf) == 0); + lockBuffer = true; + } page = BufferGetPage(buf); @@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple) /* Now insert sequence tuple */ - LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); + if (lockBuffer) + LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); /* * Since VACUUM does not process sequences, we have to force the tuple to @@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple) END_CRIT_SECTION(); - UnlockReleaseBuffer(buf); + if (lockBuffer) + UnlockReleaseBuffer(buf); } /* @@ -451,6 +460,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) init_sequence(relid, &elm, &seqrel); + if (RELATION_IS_GLOBAL_TEMP(seqrel)) + { + if (is_other_backend_use_gtt(RelationGetRelid(seqrel))) + ereport(ERROR, + (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), + errmsg("cannot alter global temporary sequence %s when other backend attached it.", + RelationGetRelationName(seqrel)))); + } + rel = table_open(SequenceRelationId, RowExclusiveLock); seqtuple = SearchSysCacheCopy1(SEQRELID, ObjectIdGetDatum(relid)); @@ -502,7 +520,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) /* * Insert the modified tuple into the new storage file. */ - fill_seq_with_data(seqrel, newdatatuple); + fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer); } /* process OWNED BY if given */ @@ -611,7 +629,7 @@ nextval_internal(Oid relid, bool check_permissions) RelationGetRelationName(seqrel)))); /* read-only transactions may only modify temp sequences */ - if (!seqrel->rd_islocaltemp) + if (!RELATION_IS_TEMP(seqrel)) PreventCommandIfReadOnly("nextval()"); /* @@ -936,7 +954,7 @@ do_setval(Oid relid, int64 next, bool iscalled) ReleaseSysCache(pgstuple); /* read-only transactions may only modify temp sequences */ - if (!seqrel->rd_islocaltemp) + if (!RELATION_IS_TEMP(seqrel)) PreventCommandIfReadOnly("setval()"); /* @@ -1154,6 +1172,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel) /* Return results */ *p_elm = elm; *p_rel = seqrel; + + if (RELATION_IS_GLOBAL_TEMP(seqrel) && + !gtt_storage_attached(RelationGetRelid(seqrel))) + { + RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel); + gtt_init_seq(seqrel); + } } @@ -1954,3 +1979,46 @@ seq_mask(char *page, BlockNumber blkno) mask_unused_space(page); } + +int64 +get_seqence_start_value(Oid seqid) +{ + HeapTuple seqtuple; + Form_pg_sequence seqform; + int64 start; + + seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid)); + if (!HeapTupleIsValid(seqtuple)) + elog(ERROR, "cache lookup failed for sequence %u", + seqid); + + seqform = (Form_pg_sequence) GETSTRUCT(seqtuple); + start = seqform->seqstart; + ReleaseSysCache(seqtuple); + + return start; +} + +void +gtt_init_seq(Relation rel) +{ + /* Initialize sequence for global temporary tables */ + Datum value[SEQ_COL_LASTCOL] = {0}; + bool null[SEQ_COL_LASTCOL] = {false}; + HeapTuple tuple; + int64 startv = get_seqence_start_value(RelationGetRelid(rel)); + + /* + * last_value from pg_sequence.seqstart + * log_cnt = 0 + * is_called = false + */ + value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */ + + tuple = heap_form_tuple(RelationGetDescr(rel), value, null); + fill_seq_with_data(rel, tuple, InvalidBuffer); + heap_freetuple(tuple); + + return; +} + diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 5745cd6..c0b0dc8 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -47,6 +47,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "catalog/toasting.h" +#include "catalog/storage_gtt.h" #include "commands/cluster.h" #include "commands/comment.h" #include "commands/defrem.h" @@ -556,6 +557,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx, Relation partitionTbl); static List *GetParentedForeignKeyRefs(Relation partition); static void ATDetachCheckNoForeignKeyRefs(Relation partition); +static OnCommitAction gtt_oncommit_option(List *options); /* ---------------------------------------------------------------- @@ -601,6 +603,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, LOCKMODE parentLockmode; const char *accessMethod = NULL; Oid accessMethodId = InvalidOid; + OnCommitAction oncommit_action = ONCOMMIT_NOOP; /* * Truncate relname to appropriate length (probably a waste of time, as @@ -611,8 +614,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, /* * Check consistency of arguments */ + /* global temp table same as local temp table */ if (stmt->oncommit != ONCOMMIT_NOOP - && stmt->relation->relpersistence != RELPERSISTENCE_TEMP) + && !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP || + stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)) ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("ON COMMIT can only be used on temporary tables"))); @@ -642,7 +647,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, * code. This is needed because calling code might not expect untrusted * tables to appear in pg_temp at the front of its search path. */ - if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP + /* global temp table same as local temp table */ + if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP || + stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) && InSecurityRestrictedOperation()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), @@ -743,6 +750,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, /* * Parse and validate reloptions, if any. */ + /* global temp table */ + oncommit_action = gtt_oncommit_option(stmt->options); + if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && + (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE)) + { + /* check parent table*/ + if (inheritOids) + { + Oid parent = linitial_oid(inheritOids); + Relation relation = table_open(parent, NoLock); + + if (!RELATION_IS_GLOBAL_TEMP(relation)) + elog(ERROR, "The parent table must be global temporary table"); + + table_close(relation, NoLock); + } + + if (oncommit_action != ONCOMMIT_NOOP) + { + if (stmt->oncommit != ONCOMMIT_NOOP) + elog(ERROR, "could not create global temporary table with on commit and with clause at same time"); + + stmt->oncommit = oncommit_action; + } + else + { + DefElem *opt = makeNode(DefElem); + + opt->type = T_DefElem; + opt->defnamespace = NULL; + opt->defname = "on_commit_delete_rows"; + opt->defaction = DEFELEM_UNSPEC; + + /* use reloptions to remember on commit clause */ + if (stmt->oncommit == ONCOMMIT_DELETE_ROWS) + opt->arg = (Node *)makeString("true"); + else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS) + opt->arg = (Node *)makeString("false"); + else if (stmt->oncommit == ONCOMMIT_NOOP) + opt->arg = (Node *)makeString("false"); + else + elog(ERROR, "global temp table not support on commit drop clause"); + + stmt->options = lappend(stmt->options, opt); + } + } + else if (oncommit_action != ONCOMMIT_NOOP) + elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table"); + reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps, true, false); @@ -1330,6 +1386,7 @@ RemoveRelations(DropStmt *drop) Oid relOid; ObjectAddress obj; struct DropRelationCallbackState state; + char rel_persistence; /* * These next few steps are a great deal like relation_openrv, but we @@ -1363,8 +1420,9 @@ RemoveRelations(DropStmt *drop) * Decide if concurrent mode needs to be used here or not. The * relation persistence cannot be known without its OID. */ + rel_persistence = get_rel_persistence(relOid); if (drop->concurrent && - get_rel_persistence(relOid) != RELPERSISTENCE_TEMP) + !(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)) { Assert(list_length(drop->objects) == 1 && drop->removeType == OBJECT_INDEX); @@ -1819,6 +1877,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged, if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) continue; + if (RELATION_IS_GLOBAL_TEMP(rel) && + !gtt_storage_attached(RelationGetRelid(rel))) + continue; + /* * Normally, we need a transaction-safe truncation here. However, if * the table was either created in the current (sub)transaction or has @@ -3570,6 +3632,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode, /* Caller is required to provide an adequate lock. */ rel = relation_open(context->relid, NoLock); + /* We allow to alter global temp table only this session use it */ + if (RELATION_IS_GLOBAL_TEMP(rel)) + { + if (is_other_backend_use_gtt(RelationGetRelid(rel))) + ereport(ERROR, + (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), + errmsg("cannot alter global temporary table %s when other backend attached it.", + RelationGetRelationName(rel)))); + } + CheckTableNotInUse(rel, "ALTER TABLE"); ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context); @@ -4847,6 +4919,38 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot rewrite temporary tables of other sessions"))); + if (RELATION_IS_GLOBAL_TEMP(OldHeap)) + { + if (tab->chgPersistence) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot change global temporary table persistence setting"))); + + /* gtt may not attached, create it */ + if(!gtt_storage_attached(tab->relid)) + { + ResultRelInfo *resultRelInfo; + MemoryContext oldcontext; + MemoryContext ctx_alter_gtt; + + ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext, + "gtt alter table", ALLOCSET_DEFAULT_SIZES); + + oldcontext = MemoryContextSwitchTo(ctx_alter_gtt); + resultRelInfo = makeNode(ResultRelInfo); + InitResultRelInfo(resultRelInfo, OldHeap, + 1, NULL, 0); + if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex && + resultRelInfo->ri_IndexRelationDescs == NULL) + ExecOpenIndices(resultRelInfo, false); + + init_gtt_storage(CMD_UTILITY, resultRelInfo); + ExecCloseIndices(resultRelInfo); + MemoryContextSwitchTo(oldcontext); + MemoryContextDelete(ctx_alter_gtt); + } + } + /* * Select destination tablespace (same as original unless user * requested a change) @@ -8184,6 +8288,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("constraints on temporary tables must involve temporary tables of this session"))); break; + case RELPERSISTENCE_GLOBAL_TEMP: + if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("constraints on global temporary tables may reference only global temporary tables"))); + break; } /* @@ -12740,6 +12850,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ + if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP) + elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command."); + pgclass = table_open(RelationRelationId, RowExclusiveLock); /* Fetch heap tuple */ @@ -12942,6 +13055,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode) */ rel = relation_open(tableOid, lockmode); + if (RELATION_IS_GLOBAL_TEMP(rel)) + elog(ERROR, "not support alter table set tablespace on global temp table"); + /* * No work if no change in tablespace. */ @@ -13316,7 +13432,7 @@ index_copy_data(Relation rel, RelFileNode newrnode) * NOTE: any conflict in relfilenode value will be caught in * RelationCreateStorage(). */ - RelationCreateStorage(newrnode, rel->rd_rel->relpersistence); + RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel); /* copy main fork */ RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM, @@ -14723,7 +14839,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged) */ switch (rel->rd_rel->relpersistence) { + /* global temp table same as local temp table */ case RELPERSISTENCE_TEMP: + case RELPERSISTENCE_GLOBAL_TEMP: ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("cannot change logged status of table \"%s\" because it is temporary", @@ -17379,3 +17497,36 @@ ATDetachCheckNoForeignKeyRefs(Relation partition) table_close(rel, NoLock); } } + +static OnCommitAction +gtt_oncommit_option(List *options) +{ + ListCell *listptr; + OnCommitAction action = ONCOMMIT_NOOP; + + foreach(listptr, options) + { + DefElem *def = (DefElem *) lfirst(listptr); + + if (strcmp(def->defname, "on_commit_delete_rows") == 0) + { + bool res = false; + char *sval = defGetString(def); + + if (!parse_bool(sval, &res)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value"))); + + if (res) + action = ONCOMMIT_DELETE_ROWS; + else + action = ONCOMMIT_PRESERVE_ROWS; + + break; + } + } + + return action; +} + diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 5a110ed..02e6f1e 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -35,6 +35,7 @@ #include "catalog/pg_database.h" #include "catalog/pg_inherits.h" #include "catalog/pg_namespace.h" +#include "catalog/storage_gtt.h" #include "commands/cluster.h" #include "commands/defrem.h" #include "commands/vacuum.h" @@ -1215,6 +1216,17 @@ vac_update_relstats(Relation relation, HeapTuple ctup; Form_pg_class pgcform; bool dirty; + bool is_gtt = false; + + /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */ + if (RELATION_IS_GLOBAL_TEMP(relation)) + { + is_gtt = true; + up_gtt_relstats(relation, + num_pages, num_tuples, + num_all_visible_pages, + frozenxid, minmulti); + } rd = table_open(RelationRelationId, RowExclusiveLock); @@ -1228,17 +1240,26 @@ vac_update_relstats(Relation relation, /* Apply statistical updates, if any, to copied tuple */ dirty = false; - if (pgcform->relpages != (int32) num_pages) + + if (is_gtt) + relation->rd_rel->relpages = (int32) num_pages; + else if (pgcform->relpages != (int32) num_pages) { pgcform->relpages = (int32) num_pages; dirty = true; } - if (pgcform->reltuples != (float4) num_tuples) + + if (is_gtt) + relation->rd_rel->reltuples = (float4) num_tuples; + else if (pgcform->reltuples != (float4) num_tuples) { pgcform->reltuples = (float4) num_tuples; dirty = true; } - if (pgcform->relallvisible != (int32) num_all_visible_pages) + + if (is_gtt) + relation->rd_rel->relallvisible = (int32) num_all_visible_pages; + else if (pgcform->relallvisible != (int32) num_all_visible_pages) { pgcform->relallvisible = (int32) num_all_visible_pages; dirty = true; @@ -1283,7 +1304,8 @@ vac_update_relstats(Relation relation, * This should match vac_update_datfrozenxid() concerning what we consider * to be "in the future". */ - if (TransactionIdIsNormal(frozenxid) && + if (!is_gtt && + TransactionIdIsNormal(frozenxid) && pgcform->relfrozenxid != frozenxid && (TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) || TransactionIdPrecedes(ReadNewTransactionId(), @@ -1294,7 +1316,8 @@ vac_update_relstats(Relation relation, } /* Similarly for relminmxid */ - if (MultiXactIdIsValid(minmulti) && + if (!is_gtt && + MultiXactIdIsValid(minmulti) && pgcform->relminmxid != minmulti && (MultiXactIdPrecedes(pgcform->relminmxid, minmulti) || MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid))) @@ -1392,6 +1415,10 @@ vac_update_datfrozenxid(void) continue; } + /* global temp table relstats not in pg_class */ + if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + continue; + /* * Some table AMs might not need per-relation xid / multixid horizons. * It therefore seems reasonable to allow relfrozenxid and relminmxid @@ -1449,6 +1476,42 @@ vac_update_datfrozenxid(void) Assert(TransactionIdIsNormal(newFrozenXid)); Assert(MultiXactIdIsValid(newMinMulti)); + /* + * Global temp table get frozenxid from MyProc + * to avoid the vacuum truncate clog that gtt need. + */ + if (max_active_gtt > 0) + { + TransactionId safe_age; + TransactionId oldest_gtt_frozenxid = + list_all_session_gtt_frozenxids(0, NULL, NULL, NULL); + + if (TransactionIdIsNormal(oldest_gtt_frozenxid)) + { + safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age; + if (safe_age < FirstNormalTransactionId) + safe_age += FirstNormalTransactionId; + + /* + * We tolerate that the minimum age of gtt is less than + * the minimum age of conventional tables, otherwise it will + * throw warning message. + */ + if (TransactionIdIsNormal(safe_age) && + TransactionIdPrecedes(safe_age, newFrozenXid)) + { + ereport(WARNING, + (errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db", + oldest_gtt_frozenxid), + errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid), + errhint("If they differ greatly, please consider cleaning up the data in global temp table."))); + } + + if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid)) + newFrozenXid = oldest_gtt_frozenxid; + } + } + /* Now fetch the pg_database tuple we need to update. */ relation = table_open(DatabaseRelationId, RowExclusiveLock); @@ -1776,6 +1839,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) return false; } + if (RELATION_IS_GLOBAL_TEMP(onerel) && + !gtt_storage_attached(RelationGetRelid(onerel))) + { + relation_close(onerel, lmode); + PopActiveSnapshot(); + CommitTransactionCommand(); + return false; + } + /* * Silently ignore tables that are temp tables of other backends --- * trying to vacuum these will lead to great unhappiness, since their diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c index 6e65103..04706ee 100644 --- a/src/backend/commands/view.c +++ b/src/backend/commands/view.c @@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("views cannot be unlogged because they do not have storage"))); + if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("views cannot be global temp because they do not have storage"))); + /* * If the user didn't explicitly ask for a temporary view, check whether * we need one implicitly. We allow TEMP to be inserted automatically as diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 4fdffad..0aa7559 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt) if (isTempNamespace(get_rel_namespace(rte->relid))) continue; + if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP) + continue; + PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt)); } diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index fb6ce49..06d9237 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -18,6 +18,7 @@ #include "catalog/partition.h" #include "catalog/pg_inherits.h" #include "catalog/pg_type.h" +#include "catalog/storage_gtt.h" #include "executor/execPartition.h" #include "executor/executor.h" #include "foreign/fdwapi.h" @@ -545,6 +546,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, (node != NULL && node->onConflictAction != ONCONFLICT_NONE)); + init_gtt_storage(mtstate->operation, leaf_part_rri); + /* * Build WITH CHECK OPTION constraints for the partition. Note that we * didn't build the withCheckOptionList for partitions within the planner, diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 20a4c47..e9f6dcd 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -42,6 +42,7 @@ #include "access/tableam.h" #include "access/xact.h" #include "catalog/catalog.h" +#include "catalog/storage_gtt.h" #include "commands/trigger.h" #include "executor/execPartition.h" #include "executor/executor.h" @@ -2379,6 +2380,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) ExecOpenIndices(resultRelInfo, node->onConflictAction != ONCONFLICT_NONE); + init_gtt_storage(operation, resultRelInfo); + /* * If this is an UPDATE and a BEFORE UPDATE trigger is present, the * trigger itself might modify the partition-key values. So arrange diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 255f56b..d94d6f0 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -591,6 +591,8 @@ static void set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) { + char relpersistence; + /* * The flag has previously been initialized to false, so we can just * return if it becomes clear that we can't safely set it. @@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel, * the rest of the necessary infrastructure right now anyway. So * for now, bail out if we see a temporary table. */ - if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP) + /* global temp table is same as local temp table */ + relpersistence = get_rel_persistence(rte->relid); + + if (relpersistence == RELPERSISTENCE_TEMP || + relpersistence == RELPERSISTENCE_GLOBAL_TEMP) return; /* diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index e664eb1..e03bfa0 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6419,7 +6419,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid) * Furthermore, any index predicate or index expressions must be parallel * safe. */ + /* global temp table is same as local temp table */ if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP || + heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || !is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) || !is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index))) { diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 2554502..04fb5d4 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -28,6 +28,7 @@ #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/heap.h" +#include "catalog/storage_gtt.h" #include "catalog/pg_am.h" #include "catalog/pg_proc.h" #include "catalog/pg_statistic_ext.h" @@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, continue; } + /* Ignore empty index for global temp table */ + if (RELATION_IS_GLOBAL_TEMP(indexRelation) && + !gtt_storage_attached(RelationGetRelid(indexRelation))) + { + index_close(indexRelation, NoLock); + continue; + } + /* * If the index is valid, but cannot yet be used, ignore it; but * mark the plan we are generating as transient. See diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index eee9c33..1891c53 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -2594,6 +2594,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("materialized views must not use temporary tables or views"))); + if (is_query_using_gtt(query)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialized views must not use global temporary tables or views"))); + /* * A materialized view would either need to save parameters for use in * maintaining/loading the data or prohibit them entirely. The latter diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 3c78f2d..edb8d9f 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -3302,17 +3302,11 @@ OptTemp: TEMPORARY { $$ = RELPERSISTENCE_TEMP; } | LOCAL TEMP { $$ = RELPERSISTENCE_TEMP; } | GLOBAL TEMPORARY { - ereport(WARNING, - (errmsg("GLOBAL is deprecated in temporary table creation"), - parser_errposition(@1))); - $$ = RELPERSISTENCE_TEMP; + $$ = RELPERSISTENCE_GLOBAL_TEMP; } | GLOBAL TEMP { - ereport(WARNING, - (errmsg("GLOBAL is deprecated in temporary table creation"), - parser_errposition(@1))); - $$ = RELPERSISTENCE_TEMP; + $$ = RELPERSISTENCE_GLOBAL_TEMP; } | UNLOGGED { $$ = RELPERSISTENCE_UNLOGGED; } | /*EMPTY*/ { $$ = RELPERSISTENCE_PERMANENT; } @@ -11648,19 +11642,13 @@ OptTempTableName: } | GLOBAL TEMPORARY opt_table qualified_name { - ereport(WARNING, - (errmsg("GLOBAL is deprecated in temporary table creation"), - parser_errposition(@1))); $$ = $4; - $$->relpersistence = RELPERSISTENCE_TEMP; + $$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP; } | GLOBAL TEMP opt_table qualified_name { - ereport(WARNING, - (errmsg("GLOBAL is deprecated in temporary table creation"), - parser_errposition(@1))); $$ = $4; - $$->relpersistence = RELPERSISTENCE_TEMP; + $$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP; } | UNLOGGED opt_table qualified_name { diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index b875a50..123945b 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref, List **colnames, List **colvars); static int specialAttNum(const char *attname); static bool isQueryUsingTempRelation_walker(Node *node, void *context); +static bool is_query_using_gtt_walker(Node *node, void *context); /* @@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context) isQueryUsingTempRelation_walker, context); } + +/* check if the query uses global temp table */ +static bool +is_query_using_gtt_walker(Node *node, void *context) +{ + if (node == NULL) + return false; + + if (IsA(node, Query)) + { + Query *query = (Query *) node; + ListCell *rtable; + + foreach(rtable, query->rtable) + { + RangeTblEntry *rte = lfirst(rtable); + + if (rte->rtekind == RTE_RELATION) + { + Relation rel = relation_open(rte->relid, AccessShareLock); + char relpersistence = rel->rd_rel->relpersistence; + + relation_close(rel, AccessShareLock); + if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + return true; + } + } + + return query_tree_walker(query, + is_query_using_gtt_walker, + context, + QTW_IGNORE_JOINALIASES); + } + + return expression_tree_walker(node, + is_query_using_gtt_walker, + context); +} + +/* check if the query uses global temp table */ +bool +is_query_using_gtt(Query *query) +{ + return is_query_using_gtt_walker((Node *) query, NULL); +} + diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 75c122f..97213ed 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column, seqstmt->sequence = makeRangeVar(snamespace, sname, -1); seqstmt->options = seqoptions; + if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + seqstmt->sequence->relpersistence = cxt->relation->relpersistence; + /* * If a sequence data type was specified, add it to the options. Prepend * to the list rather than append; in case a user supplied their own AS @@ -3108,6 +3111,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, cxt.isforeign = false; } cxt.relation = stmt->relation; + cxt.relation->relpersistence = RelationGetRelPersistence(rel); cxt.rel = rel; cxt.inhRelations = NIL; cxt.isalter = true; diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 7e97ffa..1f2ac58 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2089,6 +2089,11 @@ do_autovacuum(void) } continue; } + else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + { + /* autovacuum skip vacuum global temp table */ + continue; + } /* Fetch reloptions and the pgstat entry for this table */ relopts = extract_autovac_opts(tuple, pg_class_desc); @@ -2155,7 +2160,9 @@ do_autovacuum(void) /* * We cannot safely process other backends' temp tables, so skip 'em. */ - if (classForm->relpersistence == RELPERSISTENCE_TEMP) + /* autovacuum skip vacuum global temp table */ + if (classForm->relpersistence == RELPERSISTENCE_TEMP || + classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) continue; relid = classForm->oid; diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index f9980cf..406a25c 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -37,6 +37,7 @@ #include "access/xlog.h" #include "catalog/catalog.h" #include "catalog/storage.h" +#include "catalog/storage_gtt.h" #include "executor/instrument.h" #include "lib/binaryheap.h" #include "miscadmin.h" @@ -53,6 +54,7 @@ #include "utils/rel.h" #include "utils/resowner_private.h" #include "utils/timestamp.h" +#include "utils/guc.h" /* Note: these two macros only work on shared buffers, not local ones! */ @@ -2819,6 +2821,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln) BlockNumber RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum) { + /* + * When this backend not init gtt storage + * return 0 + */ + if (RELATION_IS_GLOBAL_TEMP(relation) && + !gtt_storage_attached(RelationGetRelid(relation))) + { + return 0; + } + switch (relation->rd_rel->relkind) { case RELKIND_SEQUENCE: diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index 427b0d5..7adb969 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -21,6 +21,7 @@ #include "access/nbtree.h" #include "access/subtrans.h" #include "access/twophase.h" +#include "catalog/storage_gtt.h" #include "commands/async.h" #include "miscadmin.h" #include "pgstat.h" @@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void) size = add_size(size, BTreeShmemSize()); size = add_size(size, SyncScanShmemSize()); size = add_size(size, AsyncShmemSize()); + size = add_size(size, active_gtt_shared_hash_size()); #ifdef EXEC_BACKEND size = add_size(size, ShmemBackendArraySize()); #endif @@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void) SUBTRANSShmemInit(); MultiXactShmemInit(); InitBufferPool(); + /* global temporary table */ + active_gtt_shared_hash_init(); /* * Set up lock manager diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index 3630006..0c8ef80 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -63,6 +63,7 @@ #include "utils/builtins.h" #include "utils/rel.h" #include "utils/snapmgr.h" +#include "utils/guc.h" #define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var)))) @@ -4086,3 +4087,77 @@ KnownAssignedXidsReset(void) LWLockRelease(ProcArrayLock); } + +/* + * search all active backend to get oldest frozenxid + * for global temp table. + */ +int +list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n) +{ + ProcArrayStruct *arrayP = procArray; + TransactionId result = InvalidTransactionId; + int index; + int flags = 0; + int i = 0; + + if (max_active_gtt <= 0) + return 0; + + if (max_size > 0) + { + Assert(pids); + Assert(xids); + Assert(n); + *n = 0; + } + + if (max_active_gtt <= 0) + return InvalidTransactionId; + + if (RecoveryInProgress()) + return InvalidTransactionId; + + flags |= PROC_IS_AUTOVACUUM; + flags |= PROC_IN_LOGICAL_DECODING; + + LWLockAcquire(ProcArrayLock, LW_SHARED); + if (max_size > 0 && max_size < arrayP->numProcs) + { + LWLockRelease(ProcArrayLock); + elog(ERROR, "list_all_gtt_frozenxids require more array"); + } + + for (index = 0; index < arrayP->numProcs; index++) + { + int pgprocno = arrayP->pgprocnos[index]; + volatile PGPROC *proc = &allProcs[pgprocno]; + volatile PGXACT *pgxact = &allPgXact[pgprocno]; + + if (pgxact->vacuumFlags & flags) + continue; + + if (proc->databaseId == MyDatabaseId && + TransactionIdIsNormal(proc->session_gtt_frozenxid)) + { + if (result == InvalidTransactionId) + result = proc->session_gtt_frozenxid; + else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result)) + result = proc->session_gtt_frozenxid; + + if (max_size > 0) + { + pids[i] = proc->pid; + xids[i] = proc->session_gtt_frozenxid; + i++; + } + } + } + LWLockRelease(ProcArrayLock); + + if (max_size > 0) + *n = i; + + return result; +} + diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 5aa19d3..b3bc455 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -395,6 +395,7 @@ InitProcess(void) MyProc->databaseId = InvalidOid; MyProc->roleId = InvalidOid; MyProc->tempNamespaceId = InvalidOid; + MyProc->session_gtt_frozenxid = InvalidTransactionId; /* init session level gtt frozenxid */ MyProc->isBackgroundWorker = IsBackgroundWorker; MyProc->delayChkpt = false; MyPgXact->vacuumFlags = 0; @@ -577,6 +578,7 @@ InitAuxiliaryProcess(void) MyProc->databaseId = InvalidOid; MyProc->roleId = InvalidOid; MyProc->tempNamespaceId = InvalidOid; + MyProc->session_gtt_frozenxid = InvalidTransactionId; /* init session level gtt frozenxid */ MyProc->isBackgroundWorker = IsBackgroundWorker; MyProc->delayChkpt = false; MyPgXact->vacuumFlags = 0; diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c index 8406644..0d99321 100644 --- a/src/backend/utils/adt/dbsize.c +++ b/src/backend/utils/adt/dbsize.c @@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS) Assert(backend != InvalidBackendId); } break; + case RELPERSISTENCE_GLOBAL_TEMP: + backend = BackendIdForTempRelations(); + break; default: elog(ERROR, "invalid relpersistence: %c", relform->relpersistence); backend = InvalidBackendId; /* placate compiler */ diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index cfb0568..5913efc 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -112,6 +112,7 @@ #include "catalog/pg_operator.h" #include "catalog/pg_statistic.h" #include "catalog/pg_statistic_ext.h" +#include "catalog/storage_gtt.h" #include "executor/nodeAgg.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -4821,12 +4822,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid, } else if (index->indpred == NIL) { - vardata->statsTuple = - SearchSysCache3(STATRELATTINH, - ObjectIdGetDatum(index->indexoid), - Int16GetDatum(pos + 1), - BoolGetDatum(false)); - vardata->freefunc = ReleaseSysCache; + char rel_persistence = get_rel_persistence(index->indexoid); + + if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP) + { + vardata->statsTuple = + get_gtt_att_statistic(index->indexoid, + Int16GetDatum(pos + 1), + false); + vardata->freefunc = release_gtt_statistic_cache; + } + else + { + vardata->statsTuple = + SearchSysCache3(STATRELATTINH, + ObjectIdGetDatum(index->indexoid), + Int16GetDatum(pos + 1), + BoolGetDatum(false)); + vardata->freefunc = ReleaseSysCache; + } if (HeapTupleIsValid(vardata->statsTuple)) { @@ -4951,15 +4965,27 @@ examine_simple_variable(PlannerInfo *root, Var *var, } else if (rte->rtekind == RTE_RELATION) { - /* - * Plain table or parent of an inheritance appendrel, so look up the - * column in pg_statistic - */ - vardata->statsTuple = SearchSysCache3(STATRELATTINH, - ObjectIdGetDatum(rte->relid), - Int16GetDatum(var->varattno), - BoolGetDatum(rte->inh)); - vardata->freefunc = ReleaseSysCache; + char rel_persistence = get_rel_persistence(rte->relid); + + if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP) + { + vardata->statsTuple = get_gtt_att_statistic(rte->relid, + var->varattno, + rte->inh); + vardata->freefunc = release_gtt_statistic_cache; + } + else + { + /* + * Plain table or parent of an inheritance appendrel, so look up the + * column in pg_statistic + */ + vardata->statsTuple = SearchSysCache3(STATRELATTINH, + ObjectIdGetDatum(rte->relid), + Int16GetDatum(var->varattno), + BoolGetDatum(rte->inh)); + vardata->freefunc = ReleaseSysCache; + } if (HeapTupleIsValid(vardata->statsTuple)) { @@ -6341,6 +6367,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, { /* Simple variable --- look to stats for the underlying table */ RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root); + char rel_persistence = get_rel_persistence(rte->relid); Assert(rte->rtekind == RTE_RELATION); relid = rte->relid; @@ -6358,6 +6385,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, !vardata.freefunc) elog(ERROR, "no function provided to release variable stats with"); } + else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP) + { + vardata.statsTuple = get_gtt_att_statistic(relid, + colnum, + rte->inh); + vardata.freefunc = release_gtt_statistic_cache; + } else { vardata.statsTuple = SearchSysCache3(STATRELATTINH, @@ -6369,6 +6403,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, } else { + char rel_persistence = get_rel_persistence(index->indexoid); + /* Expression --- maybe there are stats for the index itself */ relid = index->indexoid; colnum = 1; @@ -6384,6 +6420,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, !vardata.freefunc) elog(ERROR, "no function provided to release variable stats with"); } + else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP) + { + vardata.statsTuple = get_gtt_att_statistic(relid, + colnum, + false); + vardata.freefunc = release_gtt_statistic_cache; + } else { vardata.statsTuple = SearchSysCache3(STATRELATTINH, @@ -7302,6 +7345,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count, /* attempt to lookup stats in relation for this index column */ if (attnum != 0) { + char rel_persistence = get_rel_persistence(rte->relid); + /* Simple variable -- look to stats for the underlying table */ if (get_relation_stats_hook && (*get_relation_stats_hook) (root, rte, attnum, &vardata)) @@ -7314,6 +7359,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count, elog(ERROR, "no function provided to release variable stats with"); } + else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP) + { + vardata.statsTuple = + get_gtt_att_statistic(rte->relid, + attnum, + false); + vardata.freefunc = release_gtt_statistic_cache; + } else { vardata.statsTuple = @@ -7326,6 +7379,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count, } else { + char rel_persistence = get_rel_persistence(index->indexoid); + /* * Looks like we've found an expression column in the index. Let's * see if there's any stats for it. @@ -7345,6 +7400,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count, !vardata.freefunc) elog(ERROR, "no function provided to release variable stats with"); } + else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP) + { + vardata.statsTuple = + get_gtt_att_statistic(index->indexoid, + attnum, + false); + vardata.freefunc = release_gtt_statistic_cache; + } else { vardata.statsTuple = SearchSysCache3(STATRELATTINH, diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index a7d63f1..d8a52cb 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -35,6 +35,7 @@ #include "catalog/pg_statistic.h" #include "catalog/pg_transform.h" #include "catalog/pg_type.h" +#include "catalog/storage_gtt.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "utils/array.h" @@ -2939,6 +2940,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum) if (stawidth > 0) return stawidth; } + if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP) + { + tp = get_gtt_att_statistic(relid, attnum, false); + if (!HeapTupleIsValid(tp)) + return 0; + + stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth; + if (stawidth > 0) + return stawidth; + else + return 0; + } tp = SearchSysCache3(STATRELATTINH, ObjectIdGetDatum(relid), Int16GetDatum(attnum), diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 9f1f11d..aeb2a78 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -65,6 +65,7 @@ #include "catalog/pg_type.h" #include "catalog/schemapg.h" #include "catalog/storage.h" +#include "catalog/storage_gtt.h" #include "commands/policy.h" #include "commands/trigger.h" #include "miscadmin.h" @@ -1133,6 +1134,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) relation->rd_islocaltemp = false; } break; + case RELPERSISTENCE_GLOBAL_TEMP: + { + BlockNumber relpages = 0; + double reltuples = 0; + BlockNumber relallvisible = 0; + + relation->rd_backend = BackendIdForTempRelations(); + relation->rd_islocaltemp = false; + get_gtt_relstats(RelationGetRelid(relation), + &relpages, + &reltuples, + &relallvisible, + NULL, NULL); + relation->rd_rel->relpages = (int32)relpages; + relation->rd_rel->reltuples = (float4)reltuples; + relation->rd_rel->relallvisible = (int32)relallvisible; + } + break; default: elog(ERROR, "invalid relpersistence: %c", relation->rd_rel->relpersistence); @@ -1187,6 +1206,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) case RELKIND_PARTITIONED_INDEX: Assert(relation->rd_rel->relam != InvalidOid); RelationInitIndexAccessInfo(relation); + gtt_fix_index_state(relation); break; case RELKIND_RELATION: case RELKIND_TOASTVALUE: @@ -1317,7 +1337,17 @@ RelationInitPhysicalAddr(Relation relation) heap_freetuple(phys_tuple); } - relation->rd_node.relNode = relation->rd_rel->relfilenode; + if (RELATION_IS_GLOBAL_TEMP(relation)) + { + Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation)); + if (newrelnode != InvalidOid && + newrelnode != relation->rd_rel->relfilenode) + relation->rd_node.relNode = newrelnode; + else + relation->rd_node.relNode = relation->rd_rel->relfilenode; + } + else + relation->rd_node.relNode = relation->rd_rel->relfilenode; } else { @@ -2246,6 +2276,8 @@ RelationReloadIndexInfo(Relation relation) HeapTupleHeaderGetXmin(tuple->t_data)); ReleaseSysCache(tuple); + + gtt_fix_index_state(relation); } /* Okay, now it's valid again */ @@ -3472,6 +3504,10 @@ RelationBuildLocalRelation(const char *relname, rel->rd_backend = BackendIdForTempRelations(); rel->rd_islocaltemp = true; break; + case RELPERSISTENCE_GLOBAL_TEMP: + rel->rd_backend = BackendIdForTempRelations(); + rel->rd_islocaltemp = false; + break; default: elog(ERROR, "invalid relpersistence: %c", relpersistence); break; @@ -3579,28 +3615,34 @@ void RelationSetNewRelfilenode(Relation relation, char persistence) { Oid newrelfilenode; - Relation pg_class; - HeapTuple tuple; + Relation pg_class = NULL; + HeapTuple tuple = NULL; Form_pg_class classform; MultiXactId minmulti = InvalidMultiXactId; TransactionId freezeXid = InvalidTransactionId; RelFileNode newrnode; + bool modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation); /* Allocate a new relfilenode */ newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL, persistence); - /* - * Get a writable copy of the pg_class tuple for the given relation. - */ - pg_class = table_open(RelationRelationId, RowExclusiveLock); + if (modify_pg_class) + { + /* + * Get a writable copy of the pg_class tuple for the given relation. + */ + pg_class = table_open(RelationRelationId, RowExclusiveLock); - tuple = SearchSysCacheCopy1(RELOID, - ObjectIdGetDatum(RelationGetRelid(relation))); - if (!HeapTupleIsValid(tuple)) - elog(ERROR, "could not find tuple for relation %u", - RelationGetRelid(relation)); - classform = (Form_pg_class) GETSTRUCT(tuple); + tuple = SearchSysCacheCopy1(RELOID, + ObjectIdGetDatum(RelationGetRelid(relation))); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for relation %u", + RelationGetRelid(relation)); + classform = (Form_pg_class) GETSTRUCT(tuple); + } + else + memset(&classform, 0, sizeof(classform)); /* * Schedule unlinking of the old storage at transaction commit. @@ -3626,7 +3668,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence) /* handle these directly, at least for now */ SMgrRelation srel; - srel = RelationCreateStorage(newrnode, persistence); + srel = RelationCreateStorage(newrnode, persistence, relation); smgrclose(srel); } break; @@ -3646,6 +3688,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence) break; } + if (!modify_pg_class) + { + Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation)); + + Assert(RELATION_IS_GLOBAL_TEMP(relation)); + Assert(!RelationIsMapped(relation)); + relation->rd_node.relNode = relnode; + CacheInvalidateRelcache(relation); + } /* * If we're dealing with a mapped index, pg_class.relfilenode doesn't * change; instead we have to send the update to the relation mapper. @@ -3655,7 +3706,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence) * possibly-inaccurate values of relpages etc, but those will be fixed up * later. */ - if (RelationIsMapped(relation)) + else if (RelationIsMapped(relation)) { /* This case is only supported for indexes */ Assert(relation->rd_rel->relkind == RELKIND_INDEX); @@ -3701,9 +3752,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence) CatalogTupleUpdate(pg_class, &tuple->t_self, tuple); } - heap_freetuple(tuple); + if (modify_pg_class) + { + heap_freetuple(tuple); - table_close(pg_class, RowExclusiveLock); + table_close(pg_class, RowExclusiveLock); + } /* * Make the pg_class row change or relation map change visible. This will diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 5bdc02f..b52b51d 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -37,6 +37,7 @@ #include "catalog/namespace.h" #include "catalog/pg_authid.h" #include "catalog/storage.h" +#include "catalog/storage_gtt.h" #include "commands/async.h" #include "commands/prepare.h" #include "commands/trigger.h" @@ -142,6 +143,18 @@ char *GUC_check_errmsg_string; char *GUC_check_errdetail_string; char *GUC_check_errhint_string; +/* + * num = 0 means disable global temp table feature. + * global temp table define can still storage in catalog + * just can not use. + * num > 0 means database can management num active global temp table. + */ +#define MIN_NUM_ACTIVE_GTT 0 +#define DEFAULT_NUM_ACTIVE_GTT 1000 +#define MAX_NUM_ACTIVE_GTT 1000000 + +int max_active_gtt = MIN_NUM_ACTIVE_GTT; + static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4); static void set_config_sourcefile(const char *name, char *sourcefile, @@ -2070,6 +2083,15 @@ static struct config_bool ConfigureNamesBool[] = static struct config_int ConfigureNamesInt[] = { { + {"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED, + gettext_noop("max active global temporary table."), + NULL + }, + &max_active_gtt, + DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT, + NULL, NULL, NULL + }, + { {"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING, gettext_noop("Forces a switch to the next WAL file if a " "new file has not been started within N seconds."), @@ -2577,6 +2599,16 @@ static struct config_int ConfigureNamesInt[] = NULL, NULL, NULL }, + { + {"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("The defer check age of GTT, used to check expired data after vacuum."), + NULL + }, + &vacuum_gtt_defer_check_age, + 10000, 0, 1000000, + NULL, NULL, NULL + }, + /* * See also CheckRequiredParameterValues() if this parameter changes */ diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 5db4f57..018a9bf 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -2424,6 +2424,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo) dopt->no_unlogged_table_data) return; + /* Don't dump data in global temp table/sequence */ + if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + return; + /* Check that the data is not explicitly excluded */ if (simple_oid_list_member(&tabledata_exclude_oids, tbinfo->dobj.catId.oid)) @@ -15777,6 +15781,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) char *ftoptions = NULL; char *srvname = NULL; char *foreign = ""; + char *table_type = NULL; switch (tbinfo->relkind) { @@ -15830,9 +15835,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) binary_upgrade_set_pg_class_oids(fout, q, tbinfo->dobj.catId.oid, false); + if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED) + table_type = "UNLOGGED "; + else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + table_type = "GLOBAL TEMPORARY "; + else + table_type = ""; + appendPQExpBuffer(q, "CREATE %s%s %s", - tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ? - "UNLOGGED " : "", + table_type, reltypename, qualrelname); @@ -16226,7 +16237,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) */ if (dopt->binary_upgrade && (tbinfo->relkind == RELKIND_RELATION || - tbinfo->relkind == RELKIND_MATVIEW)) + tbinfo->relkind == RELKIND_MATVIEW) && + tbinfo->relpersistence != RELPERSISTENCE_GLOBAL_TEMP) { appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n"); appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n" @@ -17131,6 +17143,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo) PQExpBuffer query = createPQExpBuffer(); PQExpBuffer delqry = createPQExpBuffer(); char *qseqname; + bool global_temp_seq = false; qseqname = pg_strdup(fmtId(tbinfo->dobj.name)); @@ -17140,9 +17153,11 @@ dumpSequence(Archive *fout, TableInfo *tbinfo) "SELECT format_type(seqtypid, NULL), " "seqstart, seqincrement, " "seqmax, seqmin, " - "seqcache, seqcycle " - "FROM pg_catalog.pg_sequence " - "WHERE seqrelid = '%u'::oid", + "seqcache, seqcycle, c.relpersistence " + "FROM pg_catalog.pg_sequence s, " + "pg_catalog.pg_class c " + "WHERE seqrelid = '%u'::oid " + "and s.seqrelid = c.oid", tbinfo->dobj.catId.oid); } else if (fout->remoteVersion >= 80400) @@ -17187,6 +17202,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo) cache = PQgetvalue(res, 0, 5); cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0); + if (fout->remoteVersion >= 130000) + global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0); + /* Calculate default limits for a sequence of this type */ is_ascending = (incby[0] != '-'); if (strcmp(seqtype, "smallint") == 0) @@ -17261,9 +17279,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo) } else { - appendPQExpBuffer(query, - "CREATE SEQUENCE %s\n", - fmtQualifiedDumpable(tbinfo)); + appendPQExpBuffer(query, "CREATE "); + + if (global_temp_seq) + appendPQExpBuffer(query, "GLOBAL TEMP "); + + appendPQExpBuffer(query, "SEQUENCE %s\n", + fmtQualifiedDumpable(tbinfo)); if (strcmp(seqtype, "bigint") != 0) appendPQExpBuffer(query, " AS %s\n", seqtype); diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c index 00aef85..9ad71c8 100644 --- a/src/bin/pg_upgrade/check.c +++ b/src/bin/pg_upgrade/check.c @@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check) start_postmaster(&old_cluster, true); /* Extract a list of databases and tables from the old cluster */ - get_db_and_rel_infos(&old_cluster); + get_db_and_rel_infos(&old_cluster, true); init_tablespaces(); @@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check) void check_new_cluster(void) { - get_db_and_rel_infos(&new_cluster); + get_db_and_rel_infos(&new_cluster, false); check_new_cluster_is_empty(); check_databases_are_compatible(); diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c index 7e524ea..371168f 100644 --- a/src/bin/pg_upgrade/info.c +++ b/src/bin/pg_upgrade/info.c @@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db, bool is_new_db); static void free_db_and_rel_infos(DbInfoArr *db_arr); static void get_db_infos(ClusterInfo *cluster); -static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo); +static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt); static void free_rel_infos(RelInfoArr *rel_arr); static void print_db_infos(DbInfoArr *dbinfo); static void print_rel_infos(RelInfoArr *rel_arr); @@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name) * * higher level routine to generate dbinfos for the database running * on the given "port". Assumes that server is already running. + * for check object need check global temp table, + * for create object skip global temp table. */ void -get_db_and_rel_infos(ClusterInfo *cluster) +get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt) { int dbnum; @@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster) get_db_infos(cluster); for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++) - get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]); + get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt); if (cluster == &old_cluster) pg_log(PG_VERBOSE, "\nsource databases:\n"); @@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster) * This allows later processing to match up old and new databases efficiently. */ static void -get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo) +get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt) { PGconn *conn = connectToServer(cluster, dbinfo->db_name); @@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo) * output, so we have to copy that system table. It's easiest to do that * by treating it as a user table. */ - snprintf(query + strlen(query), sizeof(query) - strlen(query), - "WITH regular_heap (reloid, indtable, toastheap) AS ( " - " SELECT c.oid, 0::oid, 0::oid " - " FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n " - " ON c.relnamespace = n.oid " - " WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", " - CppAsString2(RELKIND_MATVIEW) ") AND " - /* exclude possible orphaned temp tables */ - " ((n.nspname !~ '^pg_temp_' AND " - " n.nspname !~ '^pg_toast_temp_' AND " - " n.nspname NOT IN ('pg_catalog', 'information_schema', " - " 'binary_upgrade', 'pg_toast') AND " - " c.oid >= %u::pg_catalog.oid) OR " - " (n.nspname = 'pg_catalog' AND " - " relname IN ('pg_largeobject') ))), ", - FirstNormalObjectId); + if (skip_gtt) + { + snprintf(query + strlen(query), sizeof(query) - strlen(query), + "WITH regular_heap (reloid, indtable, toastheap) AS ( " + " SELECT c.oid, 0::oid, 0::oid " + " FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n " + " ON c.relnamespace = n.oid " + " WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", " + CppAsString2(RELKIND_MATVIEW) ") AND " + /* exclude global temp tables */ + " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND " + /* exclude possible orphaned temp tables */ + " ((n.nspname !~ '^pg_temp_' AND " + " n.nspname !~ '^pg_toast_temp_' AND " + " n.nspname NOT IN ('pg_catalog', 'information_schema', " + " 'binary_upgrade', 'pg_toast') AND " + " c.oid >= %u::pg_catalog.oid) OR " + " (n.nspname = 'pg_catalog' AND " + " relname IN ('pg_largeobject') ))), ", + FirstNormalObjectId); + } + else + { + snprintf(query + strlen(query), sizeof(query) - strlen(query), + "WITH regular_heap (reloid, indtable, toastheap) AS ( " + " SELECT c.oid, 0::oid, 0::oid " + " FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n " + " ON c.relnamespace = n.oid " + " WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", " + CppAsString2(RELKIND_MATVIEW) ") AND " + /* exclude possible orphaned temp tables */ + " ((n.nspname !~ '^pg_temp_' AND " + " n.nspname !~ '^pg_toast_temp_' AND " + " n.nspname NOT IN ('pg_catalog', 'information_schema', " + " 'binary_upgrade', 'pg_toast') AND " + " c.oid >= %u::pg_catalog.oid) OR " + " (n.nspname = 'pg_catalog' AND " + " relname IN ('pg_largeobject') ))), ", + FirstNormalObjectId); + } /* * Add a CTE that collects OIDs of toast tables belonging to the tables diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c index 70194eb..8dd9e66 100644 --- a/src/bin/pg_upgrade/pg_upgrade.c +++ b/src/bin/pg_upgrade/pg_upgrade.c @@ -411,7 +411,7 @@ create_new_objects(void) set_frozenxids(true); /* update new_cluster info now that we have objects in the databases */ - get_db_and_rel_infos(&new_cluster); + get_db_and_rel_infos(&new_cluster, true); } /* @@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only) "UPDATE pg_catalog.pg_class " "SET relfrozenxid = '%u' " /* only heap, materialized view, and TOAST are vacuumed */ - "WHERE relkind IN (" + "WHERE " + " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND " + "relkind IN (" CppAsString2(RELKIND_RELATION) ", " CppAsString2(RELKIND_MATVIEW) ", " CppAsString2(RELKIND_TOASTVALUE) ")", @@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only) "UPDATE pg_catalog.pg_class " "SET relminmxid = '%u' " /* only heap, materialized view, and TOAST are vacuumed */ - "WHERE relkind IN (" + "WHERE " + " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND " + "relkind IN (" CppAsString2(RELKIND_RELATION) ", " CppAsString2(RELKIND_MATVIEW) ", " CppAsString2(RELKIND_TOASTVALUE) ")", diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h index 8b90cef..d155205 100644 --- a/src/bin/pg_upgrade/pg_upgrade.h +++ b/src/bin/pg_upgrade/pg_upgrade.h @@ -389,7 +389,7 @@ void check_loadable_libraries(void); FileNameMap *gen_db_file_maps(DbInfo *old_db, DbInfo *new_db, int *nmaps, const char *old_pgdata, const char *new_pgdata); -void get_db_and_rel_infos(ClusterInfo *cluster); +void get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt); void print_maps(FileNameMap *maps, int n, const char *db_name); diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 8dca6d8..6b3f571 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -3738,7 +3738,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys if (pset.sversion >= 90100) { appendPQExpBuffer(&buf, - ",\n CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"", + ",\n CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"", + gettext_noop("session"), gettext_noop("permanent"), gettext_noop("temporary"), gettext_noop("unlogged"), diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index f6fd623..ea87bc4 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = { {"FOREIGN TABLE", NULL, NULL, NULL}, {"FUNCTION", NULL, NULL, Query_for_list_of_functions}, {"GROUP", Query_for_list_of_roles}, + {"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE + * ... */ {"INDEX", NULL, NULL, &Query_for_list_of_indexes}, {"LANGUAGE", Query_for_list_of_languages}, {"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP}, @@ -2394,6 +2396,9 @@ psql_completion(const char *text, int start, int end) /* CREATE FOREIGN DATA WRAPPER */ else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny)) COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS"); + /* CREATE GLOBAL TEMP/TEMPORARY*/ + else if (Matches("CREATE", "GLOBAL")) + COMPLETE_WITH("TEMP", "TEMPORARY"); /* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */ /* First off we complete CREATE UNIQUE with "INDEX" */ @@ -2602,6 +2607,8 @@ psql_completion(const char *text, int start, int end) /* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */ else if (TailMatches("CREATE", "TEMP|TEMPORARY")) COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW"); + else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY")) + COMPLETE_WITH("TABLE"); /* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */ else if (TailMatches("CREATE", "UNLOGGED")) COMPLETE_WITH("TABLE", "MATERIALIZED VIEW"); diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index cbfdfe2..be39472 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname, bool mapped_relation, bool allow_system_table_mods, TransactionId *relfrozenxid, - MultiXactId *relminmxid); + MultiXactId *relminmxid, + bool skip_create_storage); extern Oid heap_create_with_catalog(const char *relname, Oid relnamespace, diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index 78b33b2..4be89f6 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class; #define RELPERSISTENCE_PERMANENT 'p' /* regular table */ #define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */ #define RELPERSISTENCE_TEMP 't' /* temporary table */ +#define RELPERSISTENCE_GLOBAL_TEMP 'g' /* global temporary table */ /* default selection for replica identity (primary key or nothing) */ #define REPLICA_IDENTITY_DEFAULT 'd' diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4bce3ad..c8236a1 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5558,6 +5558,40 @@ proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_xact_function_self_time' }, +# For global temporary table +{ oid => '4388', + descr => 'List local statistics for global temporary table', + proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u', + prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement', + proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}', + proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', + proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}', + prosrc => 'pg_get_gtt_statistics' }, +{ oid => '4389', + descr => 'List local relstats for global temporary table', + proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u', + prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid', + proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}', + proargmodes => '{i,o,o,o,o,o,o}', + proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}', + prosrc => 'pg_get_gtt_relstats' }, +{ oid => '4390', + descr => 'List attached pid for one global temporary table', + proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u', + prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid', + proallargtypes => '{oid,oid,int4}', + proargmodes => '{i,o,o}', + proargnames => '{relid,relid,pid}', + prosrc => 'pg_gtt_attached_pid' }, +{ oid => '4391', + descr => 'List those backends that have used global temporary table', + proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u', + prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '', + proallargtypes => '{int4,xid}', + proargmodes => '{o,o}', + proargnames => '{pid,relfrozenxid}', + prosrc => 'pg_list_gtt_relfrozenxids' }, + { oid => '3788', descr => 'statistics: timestamp of the current statistics snapshot', proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's', diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h index 30c38e0..7ff2408 100644 --- a/src/include/catalog/storage.h +++ b/src/include/catalog/storage.h @@ -22,7 +22,7 @@ /* GUC variables */ extern int wal_skip_threshold; -extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence); +extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel); extern void RelationDropStorage(Relation rel); extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit); extern void RelationPreTruncate(Relation rel); diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h new file mode 100644 index 0000000..902375d --- /dev/null +++ b/src/include/catalog/storage_gtt.h @@ -0,0 +1,47 @@ +/*------------------------------------------------------------------------- + * + * storage_gtt.h + * prototypes for functions in backend/catalog/storage_gtt.c + * + * src/include/catalog/storage_gtt.h + * + *------------------------------------------------------------------------- + */ +#ifndef STORAGE_GTT_H +#define STORAGE_GTT_H + +#include "access/htup.h" +#include "storage/block.h" +#include "storage/relfilenode.h" +#include "nodes/execnodes.h" +#include "utils/relcache.h" + +extern int vacuum_gtt_defer_check_age; + +extern Size active_gtt_shared_hash_size(void); +extern void active_gtt_shared_hash_init(void); +extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel); +extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit); +extern bool is_other_backend_use_gtt(Oid relid); +extern bool gtt_storage_attached(Oid relid); +extern Bitmapset *copy_active_gtt_bitmap(Oid relid); +extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts, + TupleDesc tupleDescriptor, Datum *values, bool *isnull); +extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh); +extern void release_gtt_statistic_cache(HeapTuple tup); +extern void up_gtt_relstats(Relation relation, + BlockNumber num_pages, + double num_tuples, + BlockNumber num_all_visible_pages, + TransactionId relfrozenxid, + TransactionId relminmxid); +extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples, + BlockNumber *relallvisible, TransactionId *relfrozenxid, + TransactionId *relminmxid); +extern void gtt_force_enable_index(Relation index); +extern void gtt_fix_index_state(Relation index); +extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo); +extern Oid gtt_fetch_current_relfilenode(Oid relid); +extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint); + +#endif /* STORAGE_H */ diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h index e2638ab..89a5ce4 100644 --- a/src/include/commands/sequence.h +++ b/src/include/commands/sequence.h @@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr); extern void seq_desc(StringInfo buf, XLogReaderState *rptr); extern const char *seq_identify(uint8 info); extern void seq_mask(char *pagedata, BlockNumber blkno); +extern void gtt_init_seq(Relation rel); #endif /* SEQUENCE_H */ diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h index 93f9446..14cafae 100644 --- a/src/include/parser/parse_relation.h +++ b/src/include/parser/parse_relation.h @@ -120,4 +120,7 @@ extern Oid attnumTypeId(Relation rd, int attid); extern Oid attnumCollationId(Relation rd, int attid); extern bool isQueryUsingTempRelation(Query *query); +/* global temp table check */ +extern bool is_query_using_gtt(Query *query); + #endif /* PARSE_RELATION_H */ diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h index 3f88683..f870e9a 100644 --- a/src/include/storage/bufpage.h +++ b/src/include/storage/bufpage.h @@ -405,6 +405,8 @@ do { \ #define PageClearPrunable(page) \ (((PageHeader) (page))->pd_prune_xid = InvalidTransactionId) +#define GlobalTempRelationPageIsNotInitialized(rel, page) \ + ((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page)) /* ---------------------------------------------------------------- * extern declarations diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h index 8fda8e4..172eae9 100644 --- a/src/include/storage/lwlock.h +++ b/src/include/storage/lwlock.h @@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds LWTRANCHE_TBM, LWTRANCHE_PARALLEL_APPEND, LWTRANCHE_SXACT, + LWTRANCHE_GTT_CTL, LWTRANCHE_FIRST_USER_DEFINED } BuiltinTrancheIds; diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index ae4f573..69cce92 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -117,6 +117,8 @@ struct PGPROC Oid tempNamespaceId; /* OID of temp schema this backend is * using */ + TransactionId session_gtt_frozenxid; /* session level global temp table relfrozenxid */ + bool isBackgroundWorker; /* true if background worker. */ /* diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h index a5c7d0c..a86f61f 100644 --- a/src/include/storage/procarray.h +++ b/src/include/storage/procarray.h @@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin, extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin, TransactionId *catalog_xmin); +extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n); + #endif /* PROCARRAY_H */ diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index 2819282..363fc33 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -282,6 +282,10 @@ extern int tcp_user_timeout; extern bool trace_sort; #endif +/* global temporary table */ +extern int max_active_gtt; +/* end */ + /* * Functions exported by guc.c */ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 0b5957b..0720a4c 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -57,7 +57,7 @@ typedef struct RelationData struct SMgrRelationData *rd_smgr; /* cached file handle, or NULL */ int rd_refcnt; /* reference count */ BackendId rd_backend; /* owning backend id, if temporary relation */ - bool rd_islocaltemp; /* rel is a temp rel of this session */ + bool rd_islocaltemp; /* rel is a temp rel of this session */ bool rd_isnailed; /* rel is nailed in cache */ bool rd_isvalid; /* relcache entry is valid */ bool rd_indexvalid; /* is rd_indexlist valid? (also rd_pkindex and @@ -305,6 +305,7 @@ typedef struct StdRdOptions int parallel_workers; /* max number of parallel workers */ bool vacuum_index_cleanup; /* enables index vacuuming and cleanup */ bool vacuum_truncate; /* enables vacuum to truncate a relation */ + bool on_commit_delete_rows; /* global temp table */ } StdRdOptions; #define HEAP_MIN_FILLFACTOR 10 @@ -570,11 +571,13 @@ typedef struct ViewOptions * True if relation's pages are stored in local buffers. */ #define RelationUsesLocalBuffers(relation) \ - ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \ + (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) /* * RELATION_IS_LOCAL - * If a rel is either temp or newly created in the current transaction, + * If a rel is either local temp or global temp relation + * or newly created in the current transaction, * it can be assumed to be accessible only to the current backend. * This is typically used to decide that we can skip acquiring locks. * @@ -582,6 +585,7 @@ typedef struct ViewOptions */ #define RELATION_IS_LOCAL(relation) \ ((relation)->rd_islocaltemp || \ + (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \ (relation)->rd_createSubid != InvalidSubTransactionId) /* @@ -594,6 +598,14 @@ typedef struct ViewOptions ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \ !(relation)->rd_islocaltemp) +/* + * RELATION_IS_TEMP + * Test a rel is either local temp relation of this session + * or global temp relation. + */ +#define RELATION_IS_TEMP(relation) \ + ((relation)->rd_islocaltemp || \ + (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) /* * RelationIsScannable @@ -637,6 +649,17 @@ typedef struct ViewOptions RelationNeedsWAL(relation) && \ !IsCatalogRelation(relation)) +/* global temp table implementations */ +#define RELATION_IS_GLOBAL_TEMP(relation) ((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + +#define RELATION_GTT_ON_COMMIT_DELETE(relation) \ + ((relation)->rd_options && \ + ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \ + (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \ + ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false) + +#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence) + /* routines in utils/cache/relcache.c */ extern void RelationIncrementReferenceCount(Relation rel); extern void RelationDecrementReferenceCount(Relation rel); diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out new file mode 100644 index 0000000..ca2d135 --- /dev/null +++ b/src/test/regress/expected/gtt_clean.out @@ -0,0 +1,14 @@ +reset search_path; +select pg_sleep(5); + pg_sleep +---------- + +(1 row) + +drop schema gtt cascade; +NOTICE: drop cascades to 5 other objects +DETAIL: drop cascades to table gtt.gtt1 +drop cascades to table gtt.gtt2 +drop cascades to table gtt.gtt3 +drop cascades to table gtt.gtt_t_kenyon +drop cascades to table gtt.gtt_with_seq diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out new file mode 100644 index 0000000..d6a675a --- /dev/null +++ b/src/test/regress/expected/gtt_function.out @@ -0,0 +1,383 @@ +CREATE SCHEMA IF NOT EXISTS gtt_function; +set search_path=gtt_function,sys; +create global temp table gtt1(a int primary key, b text); +create global temp table gtt_test_rename(a int primary key, b text); +create global temp table gtt2(a int primary key, b text) on commit delete rows; +create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows; +create global temp table tmp_t0(c0 tsvector,c1 varchar(100)); +create table tbl_inherits_parent( +a int not null, +b varchar(32) not null default 'Got u', +c int check (c > 0), +d date not null +); +create global temp table tbl_inherits_parent_global_temp( +a int not null, +b varchar(32) not null default 'Got u', +c int check (c > 0), +d date not null +)on commit delete rows; +CREATE global temp TABLE products ( + product_no integer PRIMARY KEY, + name text, + price numeric +); +create global temp table gtt6(n int) with (on_commit_delete_rows='true'); +begin; +insert into gtt6 values (9); +-- 1 row +select * from gtt6; + n +--- + 9 +(1 row) + +commit; +-- 0 row +select * from gtt6; + n +--- +(0 rows) + +-- ERROR +create index CONCURRENTLY idx_gtt1 on gtt1 (b); +-- ERROR +cluster gtt1 using gtt1_pkey; +-- ERROR +create table gtt1(a int primary key, b text) on commit delete rows; +ERROR: ON COMMIT can only be used on temporary tables +-- ERROR +alter table gtt1 SET TABLESPACE pg_default; +ERROR: not support alter table set tablespace on global temp table +-- ERROR +alter table gtt1 set ( on_commit_delete_rows='true'); +ERROR: table cannot add or modify on commit parameter by ALTER TABLE command. +-- ERROR +create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true); +ERROR: The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table +-- ERROR +create or replace global temp view gtt_v as select 5; +ERROR: views cannot be global temp because they do not have storage +create table foo(); +-- ERROR +alter table foo set (on_commit_delete_rows='true'); +ERROR: table cannot add or modify on commit parameter by ALTER TABLE command. +-- ok +CREATE global temp TABLE measurement ( + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +--ok +CREATE global temp TABLE p_table01 ( +id bigserial NOT NULL, +cre_time timestamp without time zone, +note varchar(30) +) PARTITION BY RANGE (cre_time) +WITH ( +OIDS = FALSE +)on commit delete rows; + +CREATE global temp TABLE p_table01_2018 +PARTITION OF p_table01 +FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows; + +CREATE global temp TABLE p_table01_2017 +PARTITION OF p_table01 +FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows; +begin; +insert into p_table01 values(1,'2018-01-02 00:00:00','test1'); +insert into p_table01 values(1,'2018-01-02 00:00:00','test2'); +select count(*) from p_table01; + count +------- + 2 +(1 row) + +commit; +select count(*) from p_table01; + count +------- + 0 +(1 row) + +--ok +CREATE global temp TABLE p_table02 ( +id bigserial NOT NULL, +cre_time timestamp without time zone, +note varchar(30) +) PARTITION BY RANGE (cre_time) +WITH ( +OIDS = FALSE +) +on commit PRESERVE rows; +CREATE global temp TABLE p_table02_2018 +PARTITION OF p_table02 +FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00'); +CREATE global temp TABLE p_table02_2017 +PARTITION OF p_table02 +FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00'); +-- ERROR +create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent); +ERROR: The parent table must be global temporary table +-- ok +create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows; +select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname; + relname | relkind | relpersistence | reloptions +---------------------------------+---------+----------------+------------------------------- + p_table01 | p | g | {on_commit_delete_rows=true} + p_table01_2017 | r | g | {on_commit_delete_rows=true} + p_table01_2018 | r | g | {on_commit_delete_rows=true} + p_table01_id_seq | S | g | + p_table02 | p | g | {on_commit_delete_rows=false} + p_table02_2017 | r | g | {on_commit_delete_rows=false} + p_table02_2018 | r | g | {on_commit_delete_rows=false} + p_table02_id_seq | S | g | + tbl_inherits_parent | r | p | + tbl_inherits_parent_global_temp | r | g | {on_commit_delete_rows=true} + tbl_inherits_partition | r | g | {on_commit_delete_rows=true} +(11 rows) + +-- ERROR +create global temp table gtt3(a int primary key, b text) on commit drop; +ERROR: global temp table not support on commit drop clause +-- ERROR +create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows; +ERROR: could not create global temporary table with on commit and with clause at same time +-- ok +create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true); +--ok +alter table gtt_test_rename rename to gtt_test_new; +-- ok +ALTER TABLE gtt_test_new ADD COLUMN address varchar(30); +-- ERROR +CREATE TABLE orders ( + order_id integer PRIMARY KEY, + product_no integer REFERENCES products (product_no), + quantity integer +); +ERROR: constraints on permanent tables may reference only permanent tables +-- ok +CREATE global temp TABLE orders ( + order_id integer PRIMARY KEY, + product_no integer REFERENCES products (product_no), + quantity integer +)on commit delete rows; +--ERROR +insert into orders values(1,1,1); +ERROR: insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey" +DETAIL: Key (product_no)=(1) is not present in table "products". +--ok +insert into products values(1,'test',1.0); +begin; +insert into orders values(1,1,1); +commit; +select count(*) from products; + count +------- + 1 +(1 row) + +select count(*) from orders; + count +------- + 0 +(1 row) + +-- ok +CREATE GLOBAL TEMPORARY TABLE mytable ( + id SERIAL PRIMARY KEY, + data text +) on commit preserve rows; +-- ok +create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows; +insert into gtt_seq (a) values(1); +insert into gtt_seq (a) values(2); +select * from gtt_seq order by id; + id | a +----+--- + 2 | 1 + 3 | 2 +(2 rows) + +truncate gtt_seq; +select * from gtt_seq order by id; + id | a +----+--- +(0 rows) + +insert into gtt_seq (a) values(3); +select * from gtt_seq order by id; + id | a +----+--- + 4 | 3 +(1 row) + +--ERROR +CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1; +ERROR: materialized views must not use global temporary tables or views +-- ok +create index idx_gtt1_1 on gtt1 using hash (a); +create index idx_tmp_t0_1 on tmp_t0 using gin (c0); +create index idx_tmp_t0_2 on tmp_t0 using gist (c0); +--ok +create global temp table gt (a SERIAL,b int); +begin; +set transaction_read_only = true; +insert into gt (b) values(1); +select * from gt; + a | b +---+--- + 1 | 1 +(1 row) + +commit; +create sequence seq_1; +CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS; +CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS; +alter table gtt_s_1 add c2 int default nextval('seq_1'); +alter table gtt_s_2 add c2 int default nextval('seq_1'); +begin; +insert into gtt_s_1 (c1)values(1); +insert into gtt_s_2 (c1)values(1); +insert into gtt_s_1 (c1)values(2); +insert into gtt_s_2 (c1)values(2); +select * from gtt_s_1 order by c1; + c1 | c2 +----+---- + 1 | 1 + 2 | 3 +(2 rows) + +commit; +select * from gtt_s_1 order by c1; + c1 | c2 +----+---- +(0 rows) + +select * from gtt_s_2 order by c1; + c1 | c2 +----+---- + 1 | 2 + 2 | 4 +(2 rows) + +--ok +create global temp table gt1(a int); +insert into gt1 values(generate_series(1,100000)); +create index idx_gt1_1 on gt1 (a); +create index idx_gt1_2 on gt1((a + 1)); +create index idx_gt1_3 on gt1((a*10),(a+a),(a-1)); +explain (costs off) select * from gt1 where a=1; + QUERY PLAN +-------------------------------------- + Bitmap Heap Scan on gt1 + Recheck Cond: (a = 1) + -> Bitmap Index Scan on idx_gt1_1 + Index Cond: (a = 1) +(4 rows) + +explain (costs off) select * from gt1 where a=200000; + QUERY PLAN +-------------------------------------- + Bitmap Heap Scan on gt1 + Recheck Cond: (a = 200000) + -> Bitmap Index Scan on idx_gt1_1 + Index Cond: (a = 200000) +(4 rows) + +explain (costs off) select * from gt1 where a*10=300; + QUERY PLAN +-------------------------------------- + Bitmap Heap Scan on gt1 + Recheck Cond: ((a * 10) = 300) + -> Bitmap Index Scan on idx_gt1_3 + Index Cond: ((a * 10) = 300) +(4 rows) + +explain (costs off) select * from gt1 where a*10=3; + QUERY PLAN +-------------------------------------- + Bitmap Heap Scan on gt1 + Recheck Cond: ((a * 10) = 3) + -> Bitmap Index Scan on idx_gt1_3 + Index Cond: ((a * 10) = 3) +(4 rows) + +analyze gt1; +explain (costs off) select * from gt1 where a=1; + QUERY PLAN +---------------------------------------- + Index Only Scan using idx_gt1_1 on gt1 + Index Cond: (a = 1) +(2 rows) + +explain (costs off) select * from gt1 where a=200000; + QUERY PLAN +---------------------------------------- + Index Only Scan using idx_gt1_1 on gt1 + Index Cond: (a = 200000) +(2 rows) + +explain (costs off) select * from gt1 where a*10=300; + QUERY PLAN +----------------------------------- + Index Scan using idx_gt1_3 on gt1 + Index Cond: ((a * 10) = 300) +(2 rows) + +explain (costs off) select * from gt1 where a*10=3; + QUERY PLAN +----------------------------------- + Index Scan using idx_gt1_3 on gt1 + Index Cond: ((a * 10) = 3) +(2 rows) + +--ok +create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1'); +create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0'); +create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t'); +create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f'); +create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes'); +create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no'); +create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y'); +create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n'); +--error +create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr'); +create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye'); +reset search_path; +drop schema gtt_function cascade; +NOTICE: drop cascades to 33 other objects +DETAIL: drop cascades to table gtt_function.gtt1 +drop cascades to table gtt_function.gtt_test_new +drop cascades to table gtt_function.gtt2 +drop cascades to table gtt_function.gtt3 +drop cascades to table gtt_function.tmp_t0 +drop cascades to table gtt_function.tbl_inherits_parent +drop cascades to table gtt_function.tbl_inherits_parent_global_temp +drop cascades to table gtt_function.products +drop cascades to table gtt_function.gtt6 +drop cascades to table gtt_function.foo +drop cascades to table gtt_function.measurement +drop cascades to table gtt_function.p_table01 +drop cascades to table gtt_function.p_table02 +drop cascades to table gtt_function.tbl_inherits_partition +drop cascades to table gtt_function.gtt5 +drop cascades to table gtt_function.orders +drop cascades to table gtt_function.mytable +drop cascades to table gtt_function.gtt_seq +drop cascades to table gtt_function.gt +drop cascades to sequence gtt_function.seq_1 +drop cascades to table gtt_function.gtt_s_1 +drop cascades to table gtt_function.gtt_s_2 +drop cascades to table gtt_function.gt1 +drop cascades to table gtt_function.gtt_test1 +drop cascades to table gtt_function.gtt_test2 +drop cascades to table gtt_function.gtt_test3 +drop cascades to table gtt_function.gtt_test4 +drop cascades to table gtt_function.gtt_test5 +drop cascades to table gtt_function.gtt_test6 +drop cascades to table gtt_function.gtt_test7 +drop cascades to table gtt_function.gtt_test8 +drop cascades to table gtt_function.gtt_test9 +drop cascades to table gtt_function.gtt_test10 diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out new file mode 100644 index 0000000..0646aae --- /dev/null +++ b/src/test/regress/expected/gtt_parallel_1.out @@ -0,0 +1,90 @@ +set search_path=gtt,sys; +select nextval('gtt_with_seq_c2_seq'); + nextval +--------- + 1 +(1 row) + +insert into gtt1 values(1, 'test1'); +select * from gtt1 order by a; + a | b +---+--- +(0 rows) + +begin; +insert into gtt1 values(1, 'test1'); +select * from gtt1 order by a; + a | b +---+------- + 1 | test1 +(1 row) + +commit; +select * from gtt1 order by a; + a | b +---+--- +(0 rows) + +begin; +insert into gtt1 values(1, 'test1'); +select * from gtt1 order by a; + a | b +---+------- + 1 | test1 +(1 row) + +rollback; +select * from gtt1 order by a; + a | b +---+--- +(0 rows) + +truncate gtt1; +select * from gtt1 order by a; + a | b +---+--- +(0 rows) + +begin; +insert into gtt1 values(1, 'test1'); +select * from gtt1 order by a; + a | b +---+------- + 1 | test1 +(1 row) + +truncate gtt1; +select * from gtt1 order by a; + a | b +---+--- +(0 rows) + +insert into gtt1 values(1, 'test1'); +rollback; +select * from gtt1 order by a; + a | b +---+--- +(0 rows) + +begin; +select * from gtt1 order by a; + a | b +---+--- +(0 rows) + +truncate gtt1; +insert into gtt1 values(1, 'test1'); +select * from gtt1 order by a; + a | b +---+------- + 1 | test1 +(1 row) + +truncate gtt1; +commit; +select * from gtt1 order by a; + a | b +---+--- +(0 rows) + +reset search_path; diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out new file mode 100644 index 0000000..2f084be --- /dev/null +++ b/src/test/regress/expected/gtt_parallel_2.out @@ -0,0 +1,343 @@ +set search_path=gtt,sys; +insert into gtt3 values(1, 'test1'); +select * from gtt3 order by a; + a | b +---+------- + 1 | test1 +(1 row) + +begin; +insert into gtt3 values(2, 'test1'); +select * from gtt3 order by a; + a | b +---+------- + 1 | test1 + 2 | test1 +(2 rows) + +commit; +select * from gtt3 order by a; + a | b +---+------- + 1 | test1 + 2 | test1 +(2 rows) + +begin; +insert into gtt3 values(3, 'test1'); +select * from gtt3 order by a; + a | b +---+------- + 1 | test1 + 2 | test1 + 3 | test1 +(3 rows) + +rollback; +select * from gtt3 order by a; + a | b +---+------- + 1 | test1 + 2 | test1 +(2 rows) + +truncate gtt3; +select * from gtt3 order by a; + a | b +---+--- +(0 rows) + +insert into gtt3 values(1, 'test1'); +select * from gtt3 order by a; + a | b +---+------- + 1 | test1 +(1 row) + +begin; +insert into gtt3 values(2, 'test2'); +select * from gtt3 order by a; + a | b +---+------- + 1 | test1 + 2 | test2 +(2 rows) + +truncate gtt3; +select * from gtt3 order by a; + a | b +---+--- +(0 rows) + +insert into gtt3 values(3, 'test3'); +update gtt3 set a = 3 where b = 'test1'; +select * from gtt3 order by a; + a | b +---+------- + 3 | test3 +(1 row) + +rollback; +select * from gtt3 order by a; + a | b +---+------- + 1 | test1 +(1 row) + +begin; +select * from gtt3 order by a; + a | b +---+------- + 1 | test1 +(1 row) + +truncate gtt3; +insert into gtt3 values(5, 'test5'); +select * from gtt3 order by a; + a | b +---+------- + 5 | test5 +(1 row) + +truncate gtt3; +insert into gtt3 values(6, 'test6'); +commit; +select * from gtt3 order by a; + a | b +---+------- + 6 | test6 +(1 row) + +truncate gtt3; +insert into gtt3 values(1); +select * from gtt3; + a | b +---+--- + 1 | +(1 row) + +begin; +insert into gtt3 values(2); +select * from gtt3; + a | b +---+--- + 1 | + 2 | +(2 rows) + +SAVEPOINT save1; +truncate gtt3; +insert into gtt3 values(3); +select * from gtt3; + a | b +---+--- + 3 | +(1 row) + +SAVEPOINT save2; +truncate gtt3; +insert into gtt3 values(4); +select * from gtt3; + a | b +---+--- + 4 | +(1 row) + +SAVEPOINT save3; +rollback to savepoint save2; +Select * from gtt3; + a | b +---+--- + 3 | +(1 row) + +insert into gtt3 values(5); +select * from gtt3; + a | b +---+--- + 3 | + 5 | +(2 rows) + +rollback; +select * from gtt3; + a | b +---+--- + 1 | +(1 row) + +truncate gtt3; +insert into gtt3 values(1); +select * from gtt3; + a | b +---+--- + 1 | +(1 row) + +begin; +insert into gtt3 values(2); +select * from gtt3; + a | b +---+--- + 1 | + 2 | +(2 rows) + +SAVEPOINT save1; +truncate gtt3; +insert into gtt3 values(3); +select * from gtt3; + a | b +---+--- + 3 | +(1 row) + +SAVEPOINT save2; +truncate gtt3; +insert into gtt3 values(4); +select * from gtt3; + a | b +---+--- + 4 | +(1 row) + +SAVEPOINT save3; +rollback to savepoint save2; +Select * from gtt3; + a | b +---+--- + 3 | +(1 row) + +insert into gtt3 values(5); +select * from gtt3; + a | b +---+--- + 3 | + 5 | +(2 rows) + +commit; +select * from gtt3; + a | b +---+--- + 3 | + 5 | +(2 rows) + +truncate gtt3; +insert into gtt3 values(generate_series(1,100000), 'testing'); +select count(*) from gtt3; + count +-------- + 100000 +(1 row) + +analyze gtt3; +explain (COSTS FALSE) select * from gtt3 where a =300; + QUERY PLAN +------------------------------------ + Index Scan using gtt3_pkey on gtt3 + Index Cond: (a = 300) +(2 rows) + +insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500); +insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000); +insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000); +insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500); +select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon'; + relname | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size +--------------+------------------+------------------+---------------+-----------------+------------------------ + gtt_t_kenyon | 450560 | 8192 | 499712 | 114688 | 614400 +(1 row) + +select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0; + relname +-------------- + gtt_t_kenyon +(1 row) + +select +c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid) +from +pg_class c +where +c.oid in +( +select +i.indexrelid as indexrelid +from +pg_index i ,pg_class cc +where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid +) +order by c.relname; + relname | pg_relation_size | pg_table_size | pg_total_relation_size +--------------------+------------------+---------------+------------------------ + idx_gtt_t_kenyon_1 | 65536 | 65536 | 65536 + idx_gtt_t_kenyon_2 | 49152 | 49152 | 49152 +(2 rows) + +select count(*) from gtt_t_kenyon; + count +------- + 2006 +(1 row) + +begin; +cluster gtt_t_kenyon using idx_gtt_t_kenyon_1; +commit; +select count(*) from gtt_t_kenyon; + count +------- + 2006 +(1 row) + +begin; +cluster gtt_t_kenyon using idx_gtt_t_kenyon_1; +rollback; +select count(*) from gtt_t_kenyon; + count +------- + 2006 +(1 row) + +vacuum full gtt_t_kenyon; +select count(*) from gtt_t_kenyon; + count +------- + 2006 +(1 row) + +begin; +truncate gtt_t_kenyon; +insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500); +cluster gtt_t_kenyon using idx_gtt_t_kenyon_1; +commit; +select count(*) from gtt_t_kenyon; + count +------- + 2000 +(1 row) + +insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000); +insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000); +insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500); +begin; +truncate gtt_t_kenyon; +insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500); +cluster gtt_t_kenyon using idx_gtt_t_kenyon_1; +rollback; +select count(*) from gtt_t_kenyon; + count +------- + 2006 +(1 row) + +insert into gtt_with_seq (c1) values(1); +select * from gtt_with_seq; + c1 | c2 +----+---- + 1 | 1 +(1 row) + +reset search_path; diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out new file mode 100644 index 0000000..8c0c376 --- /dev/null +++ b/src/test/regress/expected/gtt_prepare.out @@ -0,0 +1,10 @@ +CREATE SCHEMA IF NOT EXISTS gtt; +set search_path=gtt,sys; +create global temp table gtt1(a int primary key, b text) on commit delete rows; +create global temp table gtt2(a int primary key, b text) on commit delete rows; +create global temp table gtt3(a int primary key, b text); +create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows; +create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id); +create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark); +CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows; +reset search_path; diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out new file mode 100644 index 0000000..4420fdb --- /dev/null +++ b/src/test/regress/expected/gtt_stats.out @@ -0,0 +1,80 @@ +CREATE SCHEMA IF NOT EXISTS gtt_stats; +set search_path=gtt_stats,sys; +-- expect 0 +select count(*) from pg_gtt_attached_pids; + count +------- + 0 +(1 row) + +-- expect 0 +select count(*) from pg_list_gtt_relfrozenxids(); + count +------- + 0 +(1 row) + +create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows; +-- expect 0 +select count(*) from pg_gtt_attached_pids; + count +------- + 1 +(1 row) + +-- expect 0 +select count(*) from pg_list_gtt_relfrozenxids(); + count +------- + 2 +(1 row) + +insert into gtt values(generate_series(1,10000),'test'); +-- expect 1 +select count(*) from pg_gtt_attached_pids; + count +------- + 1 +(1 row) + +-- expect 2 +select count(*) from pg_list_gtt_relfrozenxids(); + count +------- + 2 +(1 row) + +-- expect 2 +select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename; + schemaname | tablename | relpages | reltuples | relallvisible +------------+-----------+----------+-----------+--------------- + gtt_stats | gtt | 0 | 0 | 0 + gtt_stats | gtt_pkey | 1 | 0 | 0 +(2 rows) + +-- expect 0 +select * from pg_gtt_stats order by tablename; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram +------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+---------------------- +(0 rows) + +reindex table gtt; +reindex index gtt_pkey; +analyze gtt; +select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename; + schemaname | tablename | relpages | reltuples | relallvisible +------------+-----------+----------+-----------+--------------- + gtt_stats | gtt | 55 | 10000 | 0 + gtt_stats | gtt_pkey | 30 | 10000 | 0 +(2 rows) + +select * from pg_gtt_stats order by tablename; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram +------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+---------------------- + gtt_stats | gtt | a | f | 0 | 4 | -1 | | | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} | 1 | | | + gtt_stats | gtt | b | f | 0 | 5 | 1 | {test} | {1} | | 1 | | | +(2 rows) + +reset search_path; +drop schema gtt_stats cascade; +NOTICE: drop cascades to table gtt_stats.gtt diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 8876025..f9c34d7 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname, WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist FROM pg_authid WHERE (NOT pg_authid.rolcanlogin); +pg_gtt_attached_pids| SELECT n.nspname AS schemaname, + c.relname AS tablename, + s.relid, + s.pid + FROM (pg_class c + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))), + LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid) + WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid)))); +pg_gtt_relstats| SELECT n.nspname AS schemaname, + c.relname AS tablename, + s.relfilenode, + s.relpages, + s.reltuples, + s.relallvisible, + s.relfrozenxid, + s.relminmxid + FROM (pg_class c + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))), + LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid) + WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid)))); +pg_gtt_stats| SELECT n.nspname AS schemaname, + c.relname AS tablename, + a.attname, + s.stainherit AS inherited, + s.stanullfrac AS null_frac, + s.stawidth AS avg_width, + s.stadistinct AS n_distinct, + CASE + WHEN (s.stakind1 = 1) THEN s.stavalues1 + WHEN (s.stakind2 = 1) THEN s.stavalues2 + WHEN (s.stakind3 = 1) THEN s.stavalues3 + WHEN (s.stakind4 = 1) THEN s.stavalues4 + WHEN (s.stakind5 = 1) THEN s.stavalues5 + ELSE NULL::text[] + END AS most_common_vals, + CASE + WHEN (s.stakind1 = 1) THEN s.stanumbers1 + WHEN (s.stakind2 = 1) THEN s.stanumbers2 + WHEN (s.stakind3 = 1) THEN s.stanumbers3 + WHEN (s.stakind4 = 1) THEN s.stanumbers4 + WHEN (s.stakind5 = 1) THEN s.stanumbers5 + ELSE NULL::real[] + END AS most_common_freqs, + CASE + WHEN (s.stakind1 = 2) THEN s.stavalues1 + WHEN (s.stakind2 = 2) THEN s.stavalues2 + WHEN (s.stakind3 = 2) THEN s.stavalues3 + WHEN (s.stakind4 = 2) THEN s.stavalues4 + WHEN (s.stakind5 = 2) THEN s.stavalues5 + ELSE NULL::text[] + END AS histogram_bounds, + CASE + WHEN (s.stakind1 = 3) THEN s.stanumbers1[1] + WHEN (s.stakind2 = 3) THEN s.stanumbers2[1] + WHEN (s.stakind3 = 3) THEN s.stanumbers3[1] + WHEN (s.stakind4 = 3) THEN s.stanumbers4[1] + WHEN (s.stakind5 = 3) THEN s.stanumbers5[1] + ELSE NULL::real + END AS correlation, + CASE + WHEN (s.stakind1 = 4) THEN s.stavalues1 + WHEN (s.stakind2 = 4) THEN s.stavalues2 + WHEN (s.stakind3 = 4) THEN s.stavalues3 + WHEN (s.stakind4 = 4) THEN s.stavalues4 + WHEN (s.stakind5 = 4) THEN s.stavalues5 + ELSE NULL::text[] + END AS most_common_elems, + CASE + WHEN (s.stakind1 = 4) THEN s.stanumbers1 + WHEN (s.stakind2 = 4) THEN s.stanumbers2 + WHEN (s.stakind3 = 4) THEN s.stanumbers3 + WHEN (s.stakind4 = 4) THEN s.stanumbers4 + WHEN (s.stakind5 = 4) THEN s.stanumbers5 + ELSE NULL::real[] + END AS most_common_elem_freqs, + CASE + WHEN (s.stakind1 = 5) THEN s.stanumbers1 + WHEN (s.stakind2 = 5) THEN s.stanumbers2 + WHEN (s.stakind3 = 5) THEN s.stanumbers3 + WHEN (s.stakind4 = 5) THEN s.stanumbers4 + WHEN (s.stakind5 = 5) THEN s.stanumbers5 + ELSE NULL::real[] + END AS elem_count_histogram + FROM ((pg_class c + JOIN pg_attribute a ON ((c.oid = a.attrelid))) + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))), + LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5) + WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid)))); pg_hba_file_rules| SELECT a.line_number, a.type, a.database, diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 95f1925..76b2374 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -121,3 +121,10 @@ test: fast_default # run stats by itself because its delay may be insufficient under heavy load test: stats + +# global temp table test +test: gtt_stats +test: gtt_function +test: gtt_prepare +test: gtt_parallel_1 gtt_parallel_2 +test: gtt_clean diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql new file mode 100644 index 0000000..2c8e586 --- /dev/null +++ b/src/test/regress/sql/gtt_clean.sql @@ -0,0 +1,8 @@ + + +reset search_path; + +select pg_sleep(5); + +drop schema gtt cascade; + diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql new file mode 100644 index 0000000..81f0bfc --- /dev/null +++ b/src/test/regress/sql/gtt_function.sql @@ -0,0 +1,253 @@ + +CREATE SCHEMA IF NOT EXISTS gtt_function; + +set search_path=gtt_function,sys; + +create global temp table gtt1(a int primary key, b text); + +create global temp table gtt_test_rename(a int primary key, b text); + +create global temp table gtt2(a int primary key, b text) on commit delete rows; + +create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows; + +create global temp table tmp_t0(c0 tsvector,c1 varchar(100)); + +create table tbl_inherits_parent( +a int not null, +b varchar(32) not null default 'Got u', +c int check (c > 0), +d date not null +); + +create global temp table tbl_inherits_parent_global_temp( +a int not null, +b varchar(32) not null default 'Got u', +c int check (c > 0), +d date not null +)on commit delete rows; + +CREATE global temp TABLE products ( + product_no integer PRIMARY KEY, + name text, + price numeric +); + +create global temp table gtt6(n int) with (on_commit_delete_rows='true'); + +begin; +insert into gtt6 values (9); +-- 1 row +select * from gtt6; +commit; +-- 0 row +select * from gtt6; + +-- ERROR +create index CONCURRENTLY idx_gtt1 on gtt1 (b); + +-- ERROR +cluster gtt1 using gtt1_pkey; + +-- ERROR +create table gtt1(a int primary key, b text) on commit delete rows; + +-- ERROR +alter table gtt1 SET TABLESPACE pg_default; + +-- ERROR +alter table gtt1 set ( on_commit_delete_rows='true'); + +-- ERROR +create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true); + +-- ERROR +create or replace global temp view gtt_v as select 5; + +create table foo(); +-- ERROR +alter table foo set (on_commit_delete_rows='true'); + +-- ok +CREATE global temp TABLE measurement ( + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); + +--ok +CREATE global temp TABLE p_table01 ( +id bigserial NOT NULL, +cre_time timestamp without time zone, +note varchar(30) +) PARTITION BY RANGE (cre_time) +WITH ( +OIDS = FALSE +)on commit delete rows; + +CREATE global temp TABLE p_table01_2018 +PARTITION OF p_table01 +FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows; + +CREATE global temp TABLE p_table01_2017 +PARTITION OF p_table01 +FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows; + +begin; +insert into p_table01 values(1,'2018-01-02 00:00:00','test1'); +insert into p_table01 values(1,'2018-01-02 00:00:00','test2'); +select count(*) from p_table01; +commit; + +select count(*) from p_table01; + +--ok +CREATE global temp TABLE p_table02 ( +id bigserial NOT NULL, +cre_time timestamp without time zone, +note varchar(30) +) PARTITION BY RANGE (cre_time) +WITH ( +OIDS = FALSE +) +on commit PRESERVE rows; + +CREATE global temp TABLE p_table02_2018 +PARTITION OF p_table02 +FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00'); + +CREATE global temp TABLE p_table02_2017 +PARTITION OF p_table02 +FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00'); + +-- ERROR +create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent); + +-- ok +create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows; + +select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname; + +-- ERROR +create global temp table gtt3(a int primary key, b text) on commit drop; + +-- ERROR +create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows; + +-- ok +create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true); + +--ok +alter table gtt_test_rename rename to gtt_test_new; + +-- ok +ALTER TABLE gtt_test_new ADD COLUMN address varchar(30); + +-- ERROR +CREATE TABLE orders ( + order_id integer PRIMARY KEY, + product_no integer REFERENCES products (product_no), + quantity integer +); + +-- ok +CREATE global temp TABLE orders ( + order_id integer PRIMARY KEY, + product_no integer REFERENCES products (product_no), + quantity integer +)on commit delete rows; + +--ERROR +insert into orders values(1,1,1); + +--ok +insert into products values(1,'test',1.0); + +begin; +insert into orders values(1,1,1); +commit; + +select count(*) from products; +select count(*) from orders; + +-- ok +CREATE GLOBAL TEMPORARY TABLE mytable ( + id SERIAL PRIMARY KEY, + data text +) on commit preserve rows; + +-- ok +create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows; +insert into gtt_seq (a) values(1); +insert into gtt_seq (a) values(2); +select * from gtt_seq order by id; +truncate gtt_seq; +select * from gtt_seq order by id; +insert into gtt_seq (a) values(3); +select * from gtt_seq order by id; + +--ERROR +CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1; + +-- ok +create index idx_gtt1_1 on gtt1 using hash (a); +create index idx_tmp_t0_1 on tmp_t0 using gin (c0); +create index idx_tmp_t0_2 on tmp_t0 using gist (c0); + +--ok +create global temp table gt (a SERIAL,b int); +begin; +set transaction_read_only = true; +insert into gt (b) values(1); +select * from gt; +commit; + +create sequence seq_1; +CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS; +CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS; +alter table gtt_s_1 add c2 int default nextval('seq_1'); +alter table gtt_s_2 add c2 int default nextval('seq_1'); +begin; +insert into gtt_s_1 (c1)values(1); +insert into gtt_s_2 (c1)values(1); +insert into gtt_s_1 (c1)values(2); +insert into gtt_s_2 (c1)values(2); +select * from gtt_s_1 order by c1; +commit; +select * from gtt_s_1 order by c1; +select * from gtt_s_2 order by c1; + +--ok +create global temp table gt1(a int); +insert into gt1 values(generate_series(1,100000)); +create index idx_gt1_1 on gt1 (a); +create index idx_gt1_2 on gt1((a + 1)); +create index idx_gt1_3 on gt1((a*10),(a+a),(a-1)); +explain (costs off) select * from gt1 where a=1; +explain (costs off) select * from gt1 where a=200000; +explain (costs off) select * from gt1 where a*10=300; +explain (costs off) select * from gt1 where a*10=3; +analyze gt1; +explain (costs off) select * from gt1 where a=1; +explain (costs off) select * from gt1 where a=200000; +explain (costs off) select * from gt1 where a*10=300; +explain (costs off) select * from gt1 where a*10=3; + +--ok +create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1'); +create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0'); +create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t'); +create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f'); +create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes'); +create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no'); +create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y'); +create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n'); + +--error +create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr'); +create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye'); + +reset search_path; + +drop schema gtt_function cascade; + diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql new file mode 100644 index 0000000..d05745e --- /dev/null +++ b/src/test/regress/sql/gtt_parallel_1.sql @@ -0,0 +1,44 @@ + + +set search_path=gtt,sys; + +select nextval('gtt_with_seq_c2_seq'); + +insert into gtt1 values(1, 'test1'); +select * from gtt1 order by a; + +begin; +insert into gtt1 values(1, 'test1'); +select * from gtt1 order by a; +commit; +select * from gtt1 order by a; + +begin; +insert into gtt1 values(1, 'test1'); +select * from gtt1 order by a; +rollback; +select * from gtt1 order by a; + +truncate gtt1; +select * from gtt1 order by a; + +begin; +insert into gtt1 values(1, 'test1'); +select * from gtt1 order by a; +truncate gtt1; +select * from gtt1 order by a; +insert into gtt1 values(1, 'test1'); +rollback; +select * from gtt1 order by a; + +begin; +select * from gtt1 order by a; +truncate gtt1; +insert into gtt1 values(1, 'test1'); +select * from gtt1 order by a; +truncate gtt1; +commit; +select * from gtt1 order by a; + +reset search_path; + diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql new file mode 100644 index 0000000..39cca5e --- /dev/null +++ b/src/test/regress/sql/gtt_parallel_2.sql @@ -0,0 +1,154 @@ + + +set search_path=gtt,sys; + +insert into gtt3 values(1, 'test1'); +select * from gtt3 order by a; + +begin; +insert into gtt3 values(2, 'test1'); +select * from gtt3 order by a; +commit; +select * from gtt3 order by a; + +begin; +insert into gtt3 values(3, 'test1'); +select * from gtt3 order by a; +rollback; +select * from gtt3 order by a; + +truncate gtt3; +select * from gtt3 order by a; + +insert into gtt3 values(1, 'test1'); +select * from gtt3 order by a; + +begin; +insert into gtt3 values(2, 'test2'); +select * from gtt3 order by a; +truncate gtt3; +select * from gtt3 order by a; +insert into gtt3 values(3, 'test3'); +update gtt3 set a = 3 where b = 'test1'; +select * from gtt3 order by a; +rollback; +select * from gtt3 order by a; + +begin; +select * from gtt3 order by a; +truncate gtt3; +insert into gtt3 values(5, 'test5'); +select * from gtt3 order by a; +truncate gtt3; +insert into gtt3 values(6, 'test6'); +commit; +select * from gtt3 order by a; + +truncate gtt3; +insert into gtt3 values(1); +select * from gtt3; +begin; +insert into gtt3 values(2); +select * from gtt3; +SAVEPOINT save1; +truncate gtt3; +insert into gtt3 values(3); +select * from gtt3; +SAVEPOINT save2; +truncate gtt3; +insert into gtt3 values(4); +select * from gtt3; +SAVEPOINT save3; +rollback to savepoint save2; +Select * from gtt3; +insert into gtt3 values(5); +select * from gtt3; +rollback; +select * from gtt3; + +truncate gtt3; +insert into gtt3 values(1); +select * from gtt3; +begin; +insert into gtt3 values(2); +select * from gtt3; +SAVEPOINT save1; +truncate gtt3; +insert into gtt3 values(3); +select * from gtt3; +SAVEPOINT save2; +truncate gtt3; +insert into gtt3 values(4); +select * from gtt3; +SAVEPOINT save3; +rollback to savepoint save2; +Select * from gtt3; +insert into gtt3 values(5); +select * from gtt3; +commit; +select * from gtt3; + +truncate gtt3; +insert into gtt3 values(generate_series(1,100000), 'testing'); +select count(*) from gtt3; +analyze gtt3; +explain (COSTS FALSE) select * from gtt3 where a =300; + +insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500); +insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000); +insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000); +insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500); +select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon'; +select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0; + +select +c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid) +from +pg_class c +where +c.oid in +( +select +i.indexrelid as indexrelid +from +pg_index i ,pg_class cc +where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid +) +order by c.relname; + +select count(*) from gtt_t_kenyon; +begin; +cluster gtt_t_kenyon using idx_gtt_t_kenyon_1; +commit; +select count(*) from gtt_t_kenyon; + +begin; +cluster gtt_t_kenyon using idx_gtt_t_kenyon_1; +rollback; +select count(*) from gtt_t_kenyon; + +vacuum full gtt_t_kenyon; +select count(*) from gtt_t_kenyon; + +begin; +truncate gtt_t_kenyon; +insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500); +cluster gtt_t_kenyon using idx_gtt_t_kenyon_1; +commit; +select count(*) from gtt_t_kenyon; + +insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000); +insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000); +insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500); +begin; +truncate gtt_t_kenyon; +insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500); +cluster gtt_t_kenyon using idx_gtt_t_kenyon_1; +rollback; +select count(*) from gtt_t_kenyon; + +insert into gtt_with_seq (c1) values(1); +select * from gtt_with_seq; + +reset search_path; + diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql new file mode 100644 index 0000000..dbe84d1 --- /dev/null +++ b/src/test/regress/sql/gtt_prepare.sql @@ -0,0 +1,19 @@ + +CREATE SCHEMA IF NOT EXISTS gtt; + +set search_path=gtt,sys; + +create global temp table gtt1(a int primary key, b text) on commit delete rows; + +create global temp table gtt2(a int primary key, b text) on commit delete rows; + +create global temp table gtt3(a int primary key, b text); + +create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows; +create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id); +create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark); + +CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows; + +reset search_path; + diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql new file mode 100644 index 0000000..d61b0ff --- /dev/null +++ b/src/test/regress/sql/gtt_stats.sql @@ -0,0 +1,46 @@ + +CREATE SCHEMA IF NOT EXISTS gtt_stats; + +set search_path=gtt_stats,sys; + +-- expect 0 +select count(*) from pg_gtt_attached_pids; + +-- expect 0 +select count(*) from pg_list_gtt_relfrozenxids(); + +create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows; +-- expect 0 +select count(*) from pg_gtt_attached_pids; + +-- expect 0 +select count(*) from pg_list_gtt_relfrozenxids(); + +insert into gtt values(generate_series(1,10000),'test'); + +-- expect 1 +select count(*) from pg_gtt_attached_pids; + +-- expect 2 +select count(*) from pg_list_gtt_relfrozenxids(); + +-- expect 2 +select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename; + +-- expect 0 +select * from pg_gtt_stats order by tablename; + +reindex table gtt; + +reindex index gtt_pkey; + +analyze gtt; + +select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename; + +select * from pg_gtt_stats order by tablename; + +reset search_path; + +drop schema gtt_stats cascade; + --Apple-Mail=_C4CEB335-6ADF-4AEA-B7D4-5620CD4B6984 Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset=utf-8


On Mon, Apr = 27, 2020 at 5:34 PM =E6=9B=BE=E6=96=87=E6=97=8C <wenjing.zwj@alibaba-inc.com> wrote:


2020=E5=B9=B44=E6=9C=8827=E6=97=A5 =E4=B8=8B=E5=8D=885:26=EF=BC= =8CPrabhat Sahu <prabhat.sahu@enterprisedb.com> = =E5=86=99=E9=81=93=EF=BC=9A

Hi = Wenjing,
Please check the below scenario shows different error = message with "DROP = TABLE gtt;" for gtt with and without index.
-- Session1:
postgres=3D# create global temporary table gtt1 (c1 = int);
CREATE TABLE
postgres=3D# create = global temporary table gtt2 (c1 int);
CREATE TABLE
postgres=3D# create index idx2 on gtt2(c1);
CREATE INDEX

-- = Session2:
postgres=3D# drop table gtt1;
ERROR:  can not drop relation gtt1 when other backend = attached this global temp table
postgres=3D# drop table = gtt2;
ERROR:  can not drop index gtt2 when other = backend attached this global temp = table.
For = DROP GTT, we need to drop the index on the table first. 
So the indexes on the GTT are checked first.
But the error message needs to be fixed.
Fixed in v32


wenjing





--

With = Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com




--

With Regards,
Prabhat = Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com


= --Apple-Mail=_C4CEB335-6ADF-4AEA-B7D4-5620CD4B6984-- --Apple-Mail=_CCA669D4-0143-4B16-8D35-9EA7101C37C2-- --Apple-Mail=_BBA48AD8-6132-4D22-9678-E18AD16EFAED Content-Disposition: attachment; filename=smime.p7s Content-Type: application/pkcs7-signature; name=smime.p7s Content-Transfer-Encoding: base64 MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwEAAKCCBusw ggM6MIICo6ADAgECAgEDMA0GCSqGSIb3DQEBBQUAMIGQMSYwJAYJKoZIhvcNAQkBFhdhbGlsYW5n QGFsaWJhYmEtaW5jLmNvbTETMBEGA1UEAwwKQWxpbGFuZyBDQTEMMAoGA1UECwwDSW5jMRAwDgYD VQQKDAdBbGliYWJhMREwDwYDVQQHDAhIYW5nWmhvdTERMA8GA1UECAwIWmhlSmlhbmcxCzAJBgNV BAYTAkNOMB4XDTE0MDMyMTE4MjY0OFoXDTM0MDMxNjE4MjY0OFowPzEdMBsGA1UEAwwUQWxpbGFu ZyBDbGFzcyAzIFJvb3QxDDAKBgNVBAsMA0luYzEQMA4GA1UECgwHQWxpYmFiYTCBnzANBgkqhkiG 9w0BAQEFAAOBjQAwgYkCgYEA5EmVMYbK8q7pcO0oTDWAbhetfpZGE9f8nZgF8jMjF4UhmiiaOqJ6 nbovFt1/E2eOBOWTBDDN6029KN/rejdhvarLW6X5v8J8ZTbHRSgWQEKbE6e2Cg2lRqNYjIq+jROV V7ZWXGZX3Vy53ABsRiAyE3+vu571llNnkAgHXhh+PosCAwEAAaOB8zCB8DAdBgNVHQ4EFgQUvBg5 59PY0tRSklvSA+Es4n8PWzUwgb0GA1UdIwSBtTCBsoAUbEKnBvQw5utZdaHNpiloheRi1K2hgZak gZMwgZAxJjAkBgkqhkiG9w0BCQEWF2FsaWxhbmdAYWxpYmFiYS1pbmMuY29tMRMwEQYDVQQDDApB bGlsYW5nIENBMQwwCgYDVQQLDANJbmMxEDAOBgNVBAoMB0FsaWJhYmExETAPBgNVBAcMCEhhbmda aG91MREwDwYDVQQIDAhaaGVKaWFuZzELMAkGA1UEBhMCQ06CAQEwDwYDVR0TAQH/BAUwAwEB/zAN BgkqhkiG9w0BAQUFAAOBgQAH5MIMHpvYGsFG+8FOKIhsNmgsOkGyoH0gb/g4+kYgOAic7VtCBeoO nETPOVL+vgKBsgteMfO9VCQRzbe+WQYEsr2aV7tp9wS0Jk3t4EDV7IqTrwkpTNlffFPylSkYFIM2 +nTQ3YzM7/chCBrmq/c25ORkQw0kEfqkzPmvItrphTCCA6kwggMSoAMCAQICBgFw5ynKKDANBgkq hkiG9w0BAQUFADA/MR0wGwYDVQQDDBRBbGlsYW5nIENsYXNzIDMgUm9vdDEMMAoGA1UECwwDSW5j MRAwDgYDVQQKDAdBbGliYWJhMB4XDTIwMDMxNjA2MjQwMFoXDTI1MDMxNTA2MjQwMFowgdoxKjAo BgkqhkiG9w0BCQEWG3dlbmppbmcuendqQGFsaWJhYmEtaW5jLmNvbTEOMAwGA1UEAwwFNjc4NjIx STBHBgNVBAwMQDY3ODYyfE1ETVNFUlZFUkY5N0U5RkQwLTlCQzMtNTBCMy1BMjhCLTM4MUZDNjlE QzQ0OXxmOGZmYzI1OWNmYTYxDDAKBgNVBAsMA0luYzEQMA4GA1UECgwHQWxpYmFiYTERMA8GA1UE BwwISGFuZ1pob3UxETAPBgNVBAgMCFpoZUppYW5nMQswCQYDVQQGEwJDTjCBnzANBgkqhkiG9w0B AQEFAAOBjQAwgYkCgYEAotJNaw65iOpWLohS/RrJfYTgodgDGOfiHHVQAk/NBc8ZFV1x1bPglLGv 64RbK0IxGUgLbf94UrNFEoXAZn0rbgMnqK285bNw5lb1lCI16Hd8seTEx+32ePFVjupY5jap70X4 L6z6bz/dwPUA6bcU0BHmkALremGnUN8Ep2hJXaUCAwEAAaOCARIwggEOMA8GBikBAQIEHgQFBANv c3gwEQYGKQEBAgseBAcEBTY3ODYyMDkGBikBAQIMHgQvBC1NRE1TRVJWRVJGOTdFOUZEMC05QkMz LTUwQjMtQTI4Qi0zODFGQzY5REM0NDkwGAYGKQEBAg0eBA4EDGY4ZmZjMjU5Y2ZhNjBFBgNVHSUE PjA8BggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMEBggrBgEFBQcDDQYIKwYBBQUHAw4GCCsG AQUFBwMRMAwGA1UdDwQFAwMH/4AwHQYDVR0OBBYEFFsfN1W/DAI3pTtvpex37Ekokt2NMB8GA1Ud IwQYMBaAFLwYOefT2NLUUpJb0gPhLOJ/D1s1MA0GCSqGSIb3DQEBBQUAA4GBABOVGBHKOgUsQoby QhZ0wN1Flb8v+pG843bMnPufPL51ugsw/MbQBLXyZc9ji2ZPQwtjszPyywjNXKnCryZ1Fvg6QcOH NYZv2UJf3+Ahae0EVMgLuGOjL4OA5Kvuf2IbBm4VqZE1b8yUmgeSsO/2Vo7Fe+eiXM/vy5Ip74HR Kp7CMYICFjCCAhICAQEwSTA/MR0wGwYDVQQDDBRBbGlsYW5nIENsYXNzIDMgUm9vdDEMMAoGA1UE CwwDSW5jMRAwDgYDVQQKDAdBbGliYWJhAgYBcOcpyigwDQYJYIZIAWUDBAIBBQCgggEfMBgGCSqG SIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTIwMDQyOTAzMjI0MlowLwYJKoZI hvcNAQkEMSIEIALtfowm7h+knosquAgmjLWsW5GZyNHsDIEs8XUIrOs4MFgGCSsGAQQBgjcQBDFL MEkwPzEdMBsGA1UEAwwUQWxpbGFuZyBDbGFzcyAzIFJvb3QxDDAKBgNVBAsMA0luYzEQMA4GA1UE CgwHQWxpYmFiYQIGAXDnKcooMFoGCyqGSIb3DQEJEAILMUugSTA/MR0wGwYDVQQDDBRBbGlsYW5n IENsYXNzIDMgUm9vdDEMMAoGA1UECwwDSW5jMRAwDgYDVQQKDAdBbGliYWJhAgYBcOcpyigwDQYJ KoZIhvcNAQEBBQAEgYB5mFeEVtKazhkVsqUivdKhe+W/Z0/9W/8xbgoPLyAV8Tl3QG2p0EbY9t8C 0/vbBoB9E/e7qyJarudczXn2Kin47t9Go0xNm1XXdHFQaBHCsTwdLtG2HUncDV7SV5rrpfiC06+q Im3b443sMh09Pw/KIYzUEHVaJ7DhUAqNH/yuNAAAAAAAAA== --Apple-Mail=_BBA48AD8-6132-4D22-9678-E18AD16EFAED--