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