On 01.02.2020 14:49, Tomas Vondra wrote:
Hi, this patch was marked as waiting on author since the beginning of the CF, most likely because it no longer applies (not sure). As there has been very little activity since then, I've marked it as returned with feedback. Feel free to re-submit an updated patch for 2020-03. This definitely does not mean the feature is not desirable, but my feeling is most of the discussion happens on the other thread dealing with global temp tables [1] so maybe we should keep just that one and combine the efforts. [1] https://commitfest.postgresql.org/26/2349/
New version of the patch with new method of GTT index construction is attached. Now GTT indexes are checked before query execution and are initialized using AM build method.
So now GTT is supported for all indexes, including custom indexes. -- Konstantin Knizhnik Postgres Professional:http://www.postgrespro.com The Russian Postgres Company
diff --git a/contrib/pg_prewarm/pg_prewarm.c b/contrib/pg_prewarm/pg_prewarm.c index 33e2d28b27..93059ef581 100644 --- a/contrib/pg_prewarm/pg_prewarm.c +++ b/contrib/pg_prewarm/pg_prewarm.c @@ -178,7 +178,7 @@ pg_prewarm(PG_FUNCTION_ARGS) for (block = first_block; block <= last_block; ++block) { CHECK_FOR_INTERRUPTS(); - smgrread(rel->rd_smgr, forkNumber, block, blockbuffer.data); + smgrread(rel->rd_smgr, forkNumber, block, blockbuffer.data, false); ++blocks_done; } } diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 79430d2b7b..39baddc743 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -158,6 +158,19 @@ static relopt_bool boolRelOpts[] = }, true }, + /* + * For global temp table only + * use AccessExclusiveLock for ensure safety + */ + { + { + "on_commit_delete_rows", + "global temp table on commit options", + RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED, + ShareUpdateExclusiveLock + }, + false + }, /* list terminator */ {{NULL}} }; @@ -1486,6 +1499,8 @@ bytea * default_reloptions(Datum reloptions, bool validate, relopt_kind kind) { static const relopt_parse_elt tab[] = { + {"on_commit_delete_rows", RELOPT_TYPE_BOOL, + offsetof(StdRdOptions, on_commit_delete_rows)}, {"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)}, {"autovacuum_enabled", RELOPT_TYPE_BOOL, offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)}, @@ -1586,13 +1601,17 @@ build_reloptions(Datum reloptions, bool validate, bytea * partitioned_table_reloptions(Datum reloptions, bool validate) { + static const relopt_parse_elt tab[] = { + {"on_commit_delete_rows", RELOPT_TYPE_BOOL, + offsetof(StdRdOptions, on_commit_delete_rows)} + }; /* * There are no options for partitioned tables yet, but this is able to do * some validation. */ return (bytea *) build_reloptions(reloptions, validate, RELOPT_KIND_PARTITIONED, - 0, NULL, 0); + sizeof(StdRdOptions), tab, lengthof(tab)); } /* diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 3fa4b766db..a86de5046f 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -670,6 +670,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode) * init fork of an unlogged relation. */ if (rel->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT || + rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION || (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED && forkNum == INIT_FORKNUM)) log_smgrcreate(newrnode, forkNum); diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c index 7d6acaed92..7c48e5c2ae 100644 --- a/src/backend/catalog/catalog.c +++ b/src/backend/catalog/catalog.c @@ -396,6 +396,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence) case RELPERSISTENCE_TEMP: backend = BackendIdForTempRelations(); break; + case RELPERSISTENCE_SESSION: + backend = BackendIdForSessionRelations(); + break; case RELPERSISTENCE_UNLOGGED: case RELPERSISTENCE_PERMANENT: backend = InvalidBackendId; diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 8880586c37..22ce8953fd 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -3707,7 +3707,7 @@ reindex_relation(Oid relid, int flags, int options) if (flags & REINDEX_REL_FORCE_INDEXES_UNLOGGED) persistence = RELPERSISTENCE_UNLOGGED; else if (flags & REINDEX_REL_FORCE_INDEXES_PERMANENT) - persistence = RELPERSISTENCE_PERMANENT; + persistence = rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION ? RELPERSISTENCE_SESSION : RELPERSISTENCE_PERMANENT; else persistence = rel->rd_rel->relpersistence; diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c index fddfbf1d8c..97478352ea 100644 --- a/src/backend/catalog/storage.c +++ b/src/backend/catalog/storage.c @@ -92,6 +92,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) backend = InvalidBackendId; needs_wal = false; break; + case RELPERSISTENCE_SESSION: + backend = BackendIdForSessionRelations(); + needs_wal = false; + break; case RELPERSISTENCE_PERMANENT: backend = InvalidBackendId; needs_wal = true; @@ -367,7 +371,7 @@ RelationCopyStorage(SMgrRelation src, SMgrRelation dst, /* If we got a cancel signal during the copy of the data, quit */ CHECK_FOR_INTERRUPTS(); - smgrread(src, forkNum, blkno, buf.data); + smgrread(src, forkNum, blkno, buf.data, false); if (!PageIsVerified(page, blkno)) ereport(ERROR, diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index c9e6060035..1f5e52b54a 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -1369,7 +1369,15 @@ LANGUAGE INTERNAL STRICT STABLE PARALLEL SAFE AS 'jsonb_path_query_first_tz'; + +-- +-- Statistic for global temporary tables -- + +CREATE VIEW pg_gtt_statistic AS + SELECT s.* from pg_class c,pg_gtt_statistic_for_relation(c.oid) s where c.relpersistence='s'; + + -- The default permissions for functions mean that anyone can execute them. -- A number of functions shouldn't be executable by just anyone, but rather -- than use explicit 'superuser()' checks in those functions, we use the GRANT diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index c4420ddd7f..85d8f04eeb 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -40,6 +40,7 @@ #include "commands/vacuum.h" #include "executor/executor.h" #include "foreign/fdwapi.h" +#include "funcapi.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "parser/parse_oper.h" @@ -103,7 +104,7 @@ static int acquire_inherited_sample_rows(Relation onerel, int elevel, HeapTuple *rows, int targrows, double *totalrows, double *totaldeadrows); static void update_attstats(Oid relid, bool inh, - int natts, VacAttrStats **vacattrstats); + int natts, VacAttrStats **vacattrstats, bool is_global_temp); static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull); static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull); @@ -323,6 +324,7 @@ do_analyze_rel(Relation onerel, VacuumParams *params, Oid save_userid; int save_sec_context; int save_nestlevel; + bool is_global_temp = onerel->rd_rel->relpersistence == RELPERSISTENCE_SESSION; if (inh) ereport(elevel, @@ -586,14 +588,14 @@ do_analyze_rel(Relation onerel, VacuumParams *params, * pg_statistic for columns we didn't process, we leave them alone.) */ update_attstats(RelationGetRelid(onerel), inh, - attr_cnt, vacattrstats); + attr_cnt, vacattrstats, is_global_temp); for (ind = 0; ind < nindexes; ind++) { AnlIndexData *thisdata = &indexdata[ind]; update_attstats(RelationGetRelid(Irel[ind]), false, - thisdata->attr_cnt, thisdata->vacattrstats); + thisdata->attr_cnt, thisdata->vacattrstats, is_global_temp); } /* @@ -1456,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel, * by taking a self-exclusive lock on the relation in analyze_rel(). */ static void -update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats) +update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, bool is_global_temp) { Relation sd; int attno; @@ -1558,30 +1560,42 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats) } } - /* Is there already a pg_statistic tuple for this attribute? */ - oldtup = SearchSysCache3(STATRELATTINH, - ObjectIdGetDatum(relid), - Int16GetDatum(stats->attr->attnum), - BoolGetDatum(inh)); - - if (HeapTupleIsValid(oldtup)) + if (is_global_temp) { - /* Yes, replace it */ - stup = heap_modify_tuple(oldtup, - RelationGetDescr(sd), - values, - nulls, - replaces); - ReleaseSysCache(oldtup); - CatalogTupleUpdate(sd, &stup->t_self, stup); + stup = heap_form_tuple(RelationGetDescr(sd), values, nulls); + InsertSysCache(STATRELATTINH, + ObjectIdGetDatum(relid), + Int16GetDatum(stats->attr->attnum), + BoolGetDatum(inh), + 0, + stup); } else { - /* No, insert new tuple */ - stup = heap_form_tuple(RelationGetDescr(sd), values, nulls); - CatalogTupleInsert(sd, stup); - } + /* Is there already a pg_statistic tuple for this attribute? */ + oldtup = SearchSysCache3(STATRELATTINH, + ObjectIdGetDatum(relid), + Int16GetDatum(stats->attr->attnum), + BoolGetDatum(inh)); + if (HeapTupleIsValid(oldtup)) + { + /* Yes, replace it */ + stup = heap_modify_tuple(oldtup, + RelationGetDescr(sd), + values, + nulls, + replaces); + ReleaseSysCache(oldtup); + CatalogTupleUpdate(sd, &stup->t_self, stup); + } + else + { + /* No, insert new tuple */ + stup = heap_form_tuple(RelationGetDescr(sd), values, nulls); + CatalogTupleInsert(sd, stup); + } + } heap_freetuple(stup); } @@ -2890,3 +2904,72 @@ analyze_mcv_list(int *mcv_counts, } return num_mcv; } + +PG_FUNCTION_INFO_V1(pg_gtt_statistic_for_relation); + +typedef struct +{ + int staattnum; + bool stainherit; +} PgTempStatIteratorCtx; + +Datum +pg_gtt_statistic_for_relation(PG_FUNCTION_ARGS) +{ + Oid starelid = PG_GETARG_OID(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + Tuplestorestate *tupstore; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + TupleDesc tupdesc; + bool stainherit = false; + + /* check to see if caller supports us returning a tuplestore */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + /* check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not " \ + "allowed in this context"))); + + /* Build tuplestore to hold the result rows */ + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + /* Build a tuple descriptor for our result type */ + + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + + do + { + int staattnum = 0; + while (true) + { + HeapTuple statup = SearchSysCacheCopy3(STATRELATTINH, + ObjectIdGetDatum(starelid), + Int16GetDatum(++staattnum), + BoolGetDatum(stainherit)); + if (statup != NULL) + tuplestore_puttuple(tupstore, statup); + else + break; + } + stainherit = !stainherit; + } while (stainherit); + + MemoryContextSwitchTo(oldcontext); + + tuplestore_donestoring(tupstore); + + return (Datum) 0; +} diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index e9d7a7ff79..a22a77aa5e 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -391,6 +391,13 @@ cluster_rel(Oid tableOid, Oid indexOid, int options) errmsg("cannot vacuum temporary tables of other sessions"))); } + /* not support cluster global temp table yet */ + if (OldHeap->rd_rel->relpersistence == RELPERSISTENCE_SESSION) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("not support cluster global temporary tables yet"))); + + /* * Also check for active uses of the relation in the current transaction, * including open scans and pending AFTER trigger events. @@ -1399,7 +1406,7 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, */ if (newrelpersistence == RELPERSISTENCE_UNLOGGED) reindex_flags |= REINDEX_REL_FORCE_INDEXES_UNLOGGED; - else if (newrelpersistence == RELPERSISTENCE_PERMANENT) + else if (newrelpersistence != RELPERSISTENCE_TEMP) reindex_flags |= REINDEX_REL_FORCE_INDEXES_PERMANENT; /* Report that we are now reindexing relations */ diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 6aab73bfd4..bc3c986096 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */ */ static SeqTableData *last_used_seq = NULL; -static void fill_seq_with_data(Relation rel, HeapTuple tuple); +static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf); static Relation lock_and_open_sequence(SeqTable seq); static void create_seq_hashtable(void); static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel); @@ -222,7 +222,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) /* now initialize the sequence's data */ tuple = heap_form_tuple(tupDesc, value, null); - fill_seq_with_data(rel, tuple); + fill_seq_with_data(rel, tuple, InvalidBuffer); /* process OWNED BY if given */ if (owned_by) @@ -327,7 +327,7 @@ ResetSequence(Oid seq_relid) /* * Insert the modified tuple into the new storage file. */ - fill_seq_with_data(seq_rel, tuple); + fill_seq_with_data(seq_rel, tuple, InvalidBuffer); /* Clear local cache so that we don't think we have cached numbers */ /* Note that we do not change the currval() state */ @@ -340,18 +340,21 @@ ResetSequence(Oid seq_relid) * Initialize a sequence's relation with the specified tuple as content */ static void -fill_seq_with_data(Relation rel, HeapTuple tuple) +fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf) { - Buffer buf; Page page; sequence_magic *sm; OffsetNumber offnum; + bool lockBuffer = false; /* Initialize first page of relation with special magic number */ - buf = ReadBuffer(rel, P_NEW); - Assert(BufferGetBlockNumber(buf) == 0); - + if (buf == InvalidBuffer) + { + buf = ReadBuffer(rel, P_NEW); + Assert(BufferGetBlockNumber(buf) == 0); + lockBuffer = true; + } page = BufferGetPage(buf); PageInit(page, BufferGetPageSize(buf), sizeof(sequence_magic)); @@ -360,7 +363,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple) /* Now insert sequence tuple */ - LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); + if (lockBuffer) + LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); /* * Since VACUUM does not process sequences, we have to force the tuple to @@ -410,7 +414,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple) END_CRIT_SECTION(); - UnlockReleaseBuffer(buf); + if (lockBuffer) + UnlockReleaseBuffer(buf); } /* @@ -502,7 +507,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) /* * Insert the modified tuple into the new storage file. */ - fill_seq_with_data(seqrel, newdatatuple); + fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer); } /* process OWNED BY if given */ @@ -1178,6 +1183,17 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple) LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); page = BufferGetPage(*buf); + if (GlobalTempRelationPageIsNotInitialized(rel, page)) + { + /* Initialize sequence for global temporary tables */ + Datum value[SEQ_COL_LASTCOL] = {0}; + bool null[SEQ_COL_LASTCOL] = {false}; + HeapTuple tuple; + value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(1); /* start sequence with 1 */ + tuple = heap_form_tuple(RelationGetDescr(rel), value, null); + fill_seq_with_data(rel, tuple, *buf); + } + sm = (sequence_magic *) PageGetSpecialPointer(page); if (sm->magic != SEQ_MAGIC) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index f599393473..1e4a52ee3f 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -12,6 +12,9 @@ * *------------------------------------------------------------------------- */ +#include <sys/stat.h> +#include <unistd.h> + #include "postgres.h" #include "access/attmap.h" @@ -555,6 +558,23 @@ static List *GetParentedForeignKeyRefs(Relation partition); static void ATDetachCheckNoForeignKeyRefs(Relation partition); +static bool +has_oncommit_option(List *options) +{ + ListCell *listptr; + + foreach(listptr, options) + { + DefElem *def = (DefElem *) lfirst(listptr); + + if (pg_strcasecmp(def->defname, "on_commit_delete_rows") == 0) + return true; + } + + return false; +} + + /* ---------------------------------------------------------------- * DefineRelation * Creates a new relation. @@ -598,6 +618,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, LOCKMODE parentLockmode; const char *accessMethod = NULL; Oid accessMethodId = InvalidOid; + bool has_oncommit_clause = false; /* * Truncate relname to appropriate length (probably a waste of time, as @@ -609,7 +630,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, * Check consistency of arguments */ if (stmt->oncommit != ONCOMMIT_NOOP - && stmt->relation->relpersistence != RELPERSISTENCE_TEMP) + && !IsLocalRelpersistence(stmt->relation->relpersistence)) ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("ON COMMIT can only be used on temporary tables"))); @@ -634,17 +655,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, namespaceId = RangeVarGetAndCheckCreationNamespace(stmt->relation, NoLock, NULL); - /* - * Security check: disallow creating temp tables from security-restricted - * code. This is needed because calling code might not expect untrusted - * tables to appear in pg_temp at the front of its search path. - */ - if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP - && InSecurityRestrictedOperation()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("cannot create temporary table within security-restricted operation"))); - /* * Determine the lockmode to use when scanning parents. A self-exclusive * lock is needed here. @@ -740,6 +750,38 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, /* * Parse and validate reloptions, if any. */ + /* global temp table */ + has_oncommit_clause = has_oncommit_option(stmt->options); + if (stmt->relation->relpersistence == RELPERSISTENCE_SESSION) + { + if (has_oncommit_clause) + { + if (stmt->oncommit != ONCOMMIT_NOOP) + elog(ERROR, "can not defeine global temp table with on commit and with clause at same time"); + } + else if (stmt->oncommit != ONCOMMIT_NOOP) + { + DefElem *opt = makeNode(DefElem); + + opt->type = T_DefElem; + opt->defnamespace = NULL; + opt->defname = "on_commit_delete_rows"; + opt->defaction = DEFELEM_UNSPEC; + + /* use reloptions to remember on commit clause */ + if (stmt->oncommit == ONCOMMIT_DELETE_ROWS) + opt->arg = (Node *)makeString("true"); + else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS) + opt->arg = (Node *)makeString("false"); + else + elog(ERROR, "global temp table not support on commit drop clause"); + + stmt->options = lappend(stmt->options, opt); + } + } + else if (has_oncommit_clause) + elog(ERROR, "regular table cannot specifie on_commit_delete_rows"); + reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps, true, false); @@ -1824,7 +1866,8 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged, * table or the current physical file to be thrown away anyway. */ if (rel->rd_createSubid == mySubid || - rel->rd_newRelfilenodeSubid == mySubid) + rel->rd_newRelfilenodeSubid == mySubid || + rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION) { /* Immediate, non-rollbackable truncation is OK */ heap_truncate_one_rel(rel); @@ -3511,6 +3554,26 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode) (void *) stmt); } + +static bool +CheckGlobalTempTableNotInUse(Relation rel) +{ + int id; + for (id = 1; id <= MaxBackends; id++) + { + if (id != MyBackendId) + { + struct stat fst; + char* path = relpathbackend(rel->rd_node, id, MAIN_FORKNUM); + int rc = stat(path, &fst); + pfree(path); + if (rc == 0 && fst.st_size != 0) + return false; + } + } + return true; +} + /* * AlterTable * Execute ALTER TABLE, which can be a list of subcommands @@ -3568,6 +3631,9 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode, rel = relation_open(context->relid, NoLock); CheckTableNotInUse(rel, "ALTER TABLE"); + if (rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION + && !CheckGlobalTempTableNotInUse(rel)) + elog(ERROR, "Global temp table used by active backends can not be altered"); ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context); } @@ -8169,6 +8235,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("constraints on unlogged tables may reference only permanent or unlogged tables"))); break; + case RELPERSISTENCE_SESSION: + if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_SESSION) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("constraints on session tables may reference only session tables"))); + break; case RELPERSISTENCE_TEMP: if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP) ereport(ERROR, @@ -14629,6 +14701,13 @@ ATPrepChangePersistence(Relation rel, bool toLogged) RelationGetRelationName(rel)), errtable(rel))); break; + case RELPERSISTENCE_SESSION: + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot change logged status of session table \"%s\"", + RelationGetRelationName(rel)), + errtable(rel))); + break; case RELPERSISTENCE_PERMANENT: if (toLogged) /* nothing to do */ @@ -15116,14 +15195,7 @@ PreCommit_on_commit_actions(void) /* Do nothing (there shouldn't be such entries, actually) */ break; case ONCOMMIT_DELETE_ROWS: - - /* - * If this transaction hasn't accessed any temporary - * relations, we can skip truncating ON COMMIT DELETE ROWS - * tables, as they must still be empty. - */ - if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE)) - oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid); + oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid); break; case ONCOMMIT_DROP: oids_to_drop = lappend_oid(oids_to_drop, oc->relid); diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 8286d9cf34..7a12635e9c 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -48,6 +48,7 @@ #include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" +#include "utils/rel.h" /* results of subquery_is_pushdown_safe */ @@ -618,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel, * the rest of the necessary infrastructure right now anyway. So * for now, bail out if we see a temporary table. */ - if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP) + if (IsLocalRelpersistence(get_rel_persistence(rte->relid))) return; /* diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index d6f2153593..fd4e713646 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6312,7 +6312,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid) * Furthermore, any index predicate or index expressions must be parallel * safe. */ - if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP || + if (RelationHasSessionScope(heap) || !is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) || !is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index))) { diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index d82fc5ab8b..95062ae344 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -28,6 +28,7 @@ #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/heap.h" +#include "catalog/index.h" #include "catalog/pg_am.h" #include "catalog/pg_proc.h" #include "catalog/pg_statistic_ext.h" @@ -46,6 +47,7 @@ #include "rewrite/rewriteManip.h" #include "statistics/statistics.h" #include "storage/bufmgr.h" +#include "storage/buf_internals.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/partcache.h" @@ -80,6 +82,28 @@ static void set_baserel_partition_key_exprs(Relation relation, static void set_baserel_partition_constraint(Relation relation, RelOptInfo *rel); +static bool +is_index_valid(Relation index) +{ + if (!index->rd_index->indisvalid) + return false; + + if (index->rd_rel->relpersistence == RELPERSISTENCE_SESSION) + { + Buffer metapage = ReadBuffer(index, 0); + bool isNew = PageIsNew(BufferGetPage(metapage)); + ReleaseBuffer(metapage); + if (isNew) + { + Relation heap; + DropRelFileNodeAllLocalBuffers(index->rd_smgr->smgr_rnode.node); + heap = RelationIdGetRelation(index->rd_index->indrelid); + index->rd_indam->ambuild(heap, index, BuildIndexInfo(index)); + RelationClose(heap); + } + } + return true; +} /* * get_relation_info - @@ -205,7 +229,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * still needs to insert into "invalid" indexes, if they're marked * indisready. */ - if (!index->indisvalid) + if (!is_index_valid(indexRelation)) { index_close(indexRelation, NoLock); continue; @@ -704,7 +728,7 @@ infer_arbiter_indexes(PlannerInfo *root) idxRel = index_open(indexoid, rte->rellockmode); idxForm = idxRel->rd_index; - if (!idxForm->indisvalid) + if (!is_index_valid(idxRel)) goto next; /* diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 1b0edf5d3d..787de8329a 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -3288,20 +3288,11 @@ OptTemp: TEMPORARY { $$ = RELPERSISTENCE_TEMP; } | TEMP { $$ = RELPERSISTENCE_TEMP; } | LOCAL TEMPORARY { $$ = RELPERSISTENCE_TEMP; } | LOCAL TEMP { $$ = RELPERSISTENCE_TEMP; } - | GLOBAL TEMPORARY - { - ereport(WARNING, - (errmsg("GLOBAL is deprecated in temporary table creation"), - parser_errposition(@1))); - $$ = RELPERSISTENCE_TEMP; - } - | GLOBAL TEMP - { - ereport(WARNING, - (errmsg("GLOBAL is deprecated in temporary table creation"), - parser_errposition(@1))); - $$ = RELPERSISTENCE_TEMP; - } + | GLOBAL TEMPORARY { $$ = RELPERSISTENCE_SESSION; } + | GLOBAL TEMP { $$ = RELPERSISTENCE_SESSION; } + | SESSION { $$ = RELPERSISTENCE_SESSION; } + | SESSION TEMPORARY { $$ = RELPERSISTENCE_SESSION; } + | SESSION TEMP { $$ = RELPERSISTENCE_SESSION; } | UNLOGGED { $$ = RELPERSISTENCE_UNLOGGED; } | /*EMPTY*/ { $$ = RELPERSISTENCE_PERMANENT; } ; diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index ee2d2b54a1..e7f3a20fc4 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -437,6 +437,14 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column, seqstmt->sequence = makeRangeVar(snamespace, sname, -1); seqstmt->options = seqoptions; + /* + * Why we should not always use persistence of parent table? + * Although it is prohibited to have unlogged sequences, + * unlogged tables with SERIAL fields are accepted! + */ + if (cxt->relation->relpersistence != RELPERSISTENCE_UNLOGGED) + seqstmt->sequence->relpersistence = cxt->relation->relpersistence; + /* * If a sequence data type was specified, add it to the options. Prepend * to the list rather than append; in case a user supplied their own AS diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 6d1f28c327..4074344030 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2152,7 +2152,7 @@ do_autovacuum(void) /* * We cannot safely process other backends' temp tables, so skip 'em. */ - if (classForm->relpersistence == RELPERSISTENCE_TEMP) + if (IsLocalRelpersistence(classForm->relpersistence)) continue; relid = classForm->oid; diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index aba3960481..6322984c45 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -429,7 +429,7 @@ ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref) ) -static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence, +static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence, char relkind, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, BufferAccessStrategy strategy, bool *hit); @@ -663,7 +663,7 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, * miss. */ pgstat_count_buffer_read(reln); - buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence, + buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence, reln->rd_rel->relkind, forkNum, blockNum, mode, strategy, &hit); if (hit) pgstat_count_buffer_hit(reln); @@ -691,7 +691,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum, Assert(InRecovery); - return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum, + return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, RELKIND_RELATION, forkNum, blockNum, mode, strategy, &hit); } @@ -702,7 +702,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum, * *hit is set to true if the request was satisfied from shared buffer cache. */ static Buffer -ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum, +ReadBuffer_common(SMgrRelation smgr, char relpersistence, char relkind, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, BufferAccessStrategy strategy, bool *hit) { @@ -895,7 +895,8 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum, if (track_io_timing) INSTR_TIME_SET_CURRENT(io_start); - smgrread(smgr, forkNum, blockNum, (char *) bufBlock); + smgrread(smgr, forkNum, blockNum, (char *) bufBlock, + relkind == RELKIND_INDEX); if (track_io_timing) { @@ -2943,7 +2944,7 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber *forkNum, /* If it's a local relation, it's localbuf.c's problem. */ if (RelFileNodeBackendIsTemp(rnode)) { - if (rnode.backend == MyBackendId) + if (GetRelationBackendId(rnode.backend) == MyBackendId) { for (j = 0; j < nforks; j++) DropRelFileNodeLocalBuffers(rnode.node, forkNum[j], diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c index c5b771c531..4400b211f8 100644 --- a/src/backend/storage/smgr/md.c +++ b/src/backend/storage/smgr/md.c @@ -27,12 +27,14 @@ #include "access/xlog.h" #include "access/xlogutils.h" +#include "commands/tablecmds.h" #include "commands/tablespace.h" #include "miscadmin.h" #include "pg_trace.h" #include "pgstat.h" #include "postmaster/bgwriter.h" #include "storage/bufmgr.h" +#include "storage/ipc.h" #include "storage/fd.h" #include "storage/md.h" #include "storage/relfilenode.h" @@ -40,6 +42,7 @@ #include "storage/sync.h" #include "utils/hsearch.h" #include "utils/memutils.h" +#include "utils/rel.h" /* * The magnetic disk storage manager keeps track of open file @@ -87,6 +90,19 @@ typedef struct _MdfdVec static MemoryContext MdCxt; /* context for all MdfdVec objects */ +/* + * Structure used to collect information created by this backend. + * Data of this related should be deleted on backend exit. + */ +typedef struct SessionRelation +{ + RelFileNodeBackend rnode; + ForkNumber forknum; + struct SessionRelation* next; +} SessionRelation; + + +static SessionRelation* SessionRelations; /* Populate a file tag describing an md.c segment file. */ #define INIT_MD_FILETAG(a,xx_rnode,xx_forknum,xx_segno) \ @@ -152,6 +168,60 @@ mdinit(void) ALLOCSET_DEFAULT_SIZES); } + +/* + * Delete all data of session relations and remove their pages from shared buffers. + * This function is called on backend exit. + */ +static void +TruncateSessionRelations(int code, Datum arg) +{ + SessionRelation* rel; + for (rel = SessionRelations; rel != NULL; rel = rel->next) + { + /* Delete relation files */ + mdunlink(rel->rnode, rel->forknum, false); + } +} + +/* + * Maintain information about session relations accessed by this backend. + * This list is needed to perform cleanup on backend exit. + * Session relation is linked in this list when this relation is created or opened and file doesn't exist. + * Such procedure guarantee that each relation is linked into list only once. + */ +static void +RegisterSessionRelation(SMgrRelation reln, ForkNumber forknum) +{ + SessionRelation* rel = (SessionRelation*)MemoryContextAlloc(TopMemoryContext, sizeof(SessionRelation)); + + /* + * Perform session relation cleanup on backend exit. We are using shared memory hook, because + * cleanup should be performed before backend is disconnected from shared memory. + */ + if (SessionRelations == NULL) + on_shmem_exit(TruncateSessionRelations, 0); + + rel->rnode = reln->smgr_rnode; + rel->forknum = forknum; + rel->next = SessionRelations; + SessionRelations = rel; +} + +static void +RegisterOnCommitAction(SMgrRelation reln, ForkNumber forknum) +{ + if (reln->smgr_owner && forknum == MAIN_FORKNUM) + { + Relation rel = (Relation)((char*)reln->smgr_owner - offsetof(RelationData, rd_smgr)); + if (rel->rd_options + && ((StdRdOptions *)rel->rd_options)->on_commit_delete_rows) + { + register_on_commit_action(rel->rd_id, ONCOMMIT_DELETE_ROWS); + } + } +} + /* * mdexists() -- Does the physical file exist? * @@ -218,6 +288,8 @@ mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo) errmsg("could not create file \"%s\": %m", path))); } } + if (RelFileNodeBackendIsGlobalTemp(reln->smgr_rnode)) + RegisterSessionRelation(reln, forkNum); pfree(path); @@ -465,6 +537,21 @@ mdopenfork(SMgrRelation reln, ForkNumber forknum, int behavior) if (fd < 0) { + /* + * In case of session relation access, there may be no yet files of this relation for this backend. + * If so, then create file and register session relation for truncation on backend exit. + */ + if (RelFileNodeBackendIsGlobalTemp(reln->smgr_rnode)) + { + fd = PathNameOpenFile(path, O_RDWR | PG_BINARY | O_CREAT); + if (fd >= 0) + { + RegisterSessionRelation(reln, forknum); + if (!(behavior & EXTENSION_RETURN_NULL)) + RegisterOnCommitAction(reln, forknum); + goto NewSegment; + } + } if ((behavior & EXTENSION_RETURN_NULL) && FILE_POSSIBLY_DELETED(errno)) { @@ -476,6 +563,7 @@ mdopenfork(SMgrRelation reln, ForkNumber forknum, int behavior) errmsg("could not open file \"%s\": %m", path))); } + NewSegment: pfree(path); _fdvec_resize(reln, forknum, 1); @@ -599,7 +687,7 @@ mdwriteback(SMgrRelation reln, ForkNumber forknum, */ void mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, - char *buffer) + char *buffer, bool skipInit) { off_t seekpos; int nbytes; @@ -644,8 +732,13 @@ mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, * complaining. This allows, for example, the case of trying to * update a block that was later truncated away. */ - if (zero_damaged_pages || InRecovery) + if (zero_damaged_pages || InRecovery || RelFileNodeBackendIsGlobalTemp(reln->smgr_rnode)) + { MemSet(buffer, 0, BLCKSZ); + /* In case of session relation we need to write zero page to provide correct result of subsequent mdnblocks */ + if (RelFileNodeBackendIsGlobalTemp(reln->smgr_rnode) && !skipInit) + mdwrite(reln, forknum, blocknum, buffer, true); + } else ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), @@ -735,7 +828,8 @@ mdnblocks(SMgrRelation reln, ForkNumber forknum) BlockNumber segno = 0; /* mdopen has opened the first segment */ - Assert(reln->md_num_open_segs[forknum] > 0); + if (reln->md_num_open_segs[forknum] == 0) + return 0; /* * Start from the last open segments, to avoid redundant seeks. We have diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c index 360b5bf5bf..a7b491b8d5 100644 --- a/src/backend/storage/smgr/smgr.c +++ b/src/backend/storage/smgr/smgr.c @@ -52,7 +52,7 @@ typedef struct f_smgr void (*smgr_prefetch) (SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); void (*smgr_read) (SMgrRelation reln, ForkNumber forknum, - BlockNumber blocknum, char *buffer); + BlockNumber blocknum, char *buffer, bool skipInit); void (*smgr_write) (SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer, bool skipFsync); void (*smgr_writeback) (SMgrRelation reln, ForkNumber forknum, @@ -506,9 +506,9 @@ smgrprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum) */ void smgrread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, - char *buffer) + char *buffer, bool skipInit) { - smgrsw[reln->smgr_which].smgr_read(reln, forknum, blocknum, buffer); + smgrsw[reln->smgr_which].smgr_read(reln, forknum, blocknum, buffer, skipInit); } /* diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c index 840664429e..0416549679 100644 --- a/src/backend/utils/adt/dbsize.c +++ b/src/backend/utils/adt/dbsize.c @@ -994,6 +994,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS) /* Determine owning backend. */ switch (relform->relpersistence) { + case RELPERSISTENCE_SESSION: + backend = BackendIdForSessionRelations(); + break; case RELPERSISTENCE_UNLOGGED: case RELPERSISTENCE_PERMANENT: backend = InvalidBackendId; diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c index 64776e3209..4996d8855c 100644 --- a/src/backend/utils/cache/catcache.c +++ b/src/backend/utils/cache/catcache.c @@ -1191,6 +1191,110 @@ SearchCatCache4(CatCache *cache, return SearchCatCacheInternal(cache, 4, v1, v2, v3, v4); } + +void InsertCatCache(CatCache *cache, + Datum v1, Datum v2, Datum v3, Datum v4, + HeapTuple tuple) +{ + Datum arguments[CATCACHE_MAXKEYS]; + uint32 hashValue; + Index hashIndex; + CatCTup *ct; + dlist_iter iter; + dlist_head *bucket; + int nkeys = cache->cc_nkeys; + MemoryContext oldcxt; + + /* + * one-time startup overhead for each cache + */ + if (unlikely(cache->cc_tupdesc == NULL)) + CatalogCacheInitializeCache(cache); + + /* Initialize local parameter array */ + arguments[0] = v1; + arguments[1] = v2; + arguments[2] = v3; + arguments[3] = v4; + /* + * find the hash bucket in which to look for the tuple + */ + hashValue = CatalogCacheComputeHashValue(cache, nkeys, v1, v2, v3, v4); + hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets); + + /* + * scan the hash bucket until we find a match or exhaust our tuples + * + * Note: it's okay to use dlist_foreach here, even though we modify the + * dlist within the loop, because we don't continue the loop afterwards. + */ + bucket = &cache->cc_bucket[hashIndex]; + dlist_foreach(iter, bucket) + { + ct = dlist_container(CatCTup, cache_elem, iter.cur); + + if (ct->dead) + continue; /* ignore dead entries */ + + if (ct->hash_value != hashValue) + continue; /* quickly skip entry if wrong hash val */ + + if (!CatalogCacheCompareTuple(cache, nkeys, ct->keys, arguments)) + continue; + + /* + * If it's a positive entry, bump its refcount and return it. If it's + * negative, we can report failure to the caller. + */ + if (ct->tuple.t_len == tuple->t_len) + { + memcpy((char *) ct->tuple.t_data, + (const char *) tuple->t_data, + tuple->t_len); + return; + } + dlist_delete(&ct->cache_elem); + pfree(ct); + cache->cc_ntup -= 1; + CacheHdr->ch_ntup -= 1; + break; + } + /* Allocate memory for CatCTup and the cached tuple in one go */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + + ct = (CatCTup *) palloc(sizeof(CatCTup) + + MAXIMUM_ALIGNOF + tuple->t_len); + ct->tuple.t_len = tuple->t_len; + ct->tuple.t_self = tuple->t_self; + ct->tuple.t_tableOid = tuple->t_tableOid; + ct->tuple.t_data = (HeapTupleHeader) + MAXALIGN(((char *) ct) + sizeof(CatCTup)); + /* copy tuple contents */ + memcpy((char *) ct->tuple.t_data, + (const char *) tuple->t_data, + tuple->t_len); + ct->ct_magic = CT_MAGIC; + ct->my_cache = cache; + ct->c_list = NULL; + ct->refcount = 1; /* pinned*/ + ct->dead = false; + ct->negative = false; + ct->hash_value = hashValue; + dlist_push_head(&cache->cc_bucket[hashIndex], &ct->cache_elem); + memcpy(ct->keys, arguments, nkeys*sizeof(Datum)); + + cache->cc_ntup++; + CacheHdr->ch_ntup++; + MemoryContextSwitchTo(oldcxt); + + /* + * If the hash table has become too full, enlarge the buckets array. Quite + * arbitrarily, we enlarge when fill factor > 2. + */ + if (cache->cc_ntup > cache->cc_nbuckets * 2) + RehashCatCache(cache); +} + /* * Work-horse for SearchCatCache/SearchCatCacheN. */ diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index df025a5a30..dd0b1ff32f 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -1092,6 +1092,10 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; switch (relation->rd_rel->relpersistence) { + case RELPERSISTENCE_SESSION: + relation->rd_backend = BackendIdForSessionRelations(); + relation->rd_islocaltemp = false; + break; case RELPERSISTENCE_UNLOGGED: case RELPERSISTENCE_PERMANENT: relation->rd_backend = InvalidBackendId; @@ -3303,6 +3307,10 @@ RelationBuildLocalRelation(const char *relname, rel->rd_rel->relpersistence = relpersistence; switch (relpersistence) { + case RELPERSISTENCE_SESSION: + rel->rd_backend = BackendIdForSessionRelations(); + rel->rd_islocaltemp = false; + break; case RELPERSISTENCE_UNLOGGED: case RELPERSISTENCE_PERMANENT: rel->rd_backend = InvalidBackendId; diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 53d9ddf159..f263b8318c 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -1156,6 +1156,16 @@ SearchSysCache4(int cacheId, return SearchCatCache4(SysCache[cacheId], key1, key2, key3, key4); } +void +InsertSysCache(int cacheId, + Datum key1, Datum key2, Datum key3, Datum key4, + HeapTuple value) +{ + Assert(cacheId >= 0 && cacheId < SysCacheSize && + PointerIsValid(SysCache[cacheId])); + InsertCatCache(SysCache[cacheId], key1, key2, key3, key4, value); +} + /* * ReleaseSysCache * Release previously grabbed reference count on a tuple diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c index b7eee3da1d..afe22b2b9f 100644 --- a/src/backend/utils/fmgr/funcapi.c +++ b/src/backend/utils/fmgr/funcapi.c @@ -18,6 +18,7 @@ #include "catalog/namespace.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" +#include "catalog/pg_statistic_d.h" #include "funcapi.h" #include "nodes/nodeFuncs.h" #include "parser/parse_coerce.h" @@ -30,6 +31,13 @@ #include "utils/syscache.h" #include "utils/typcache.h" +/* + * TODO: find less ugly way to declare core function returning pg_statistics. + * OID of pg_gtt_statistic_for_relation. This function should be handled in special way because it returns set of pg_statistics + * which contains attributes of anyarray type. Type of attributes can not be deduced from input parameters and + * it prevents using tuple descriptor in this case. + */ +#define GttStatisticFunctionId 3434 static void shutdown_MultiFuncCall(Datum arg); static TypeFuncClass internal_get_result_type(Oid funcid, @@ -341,7 +349,8 @@ internal_get_result_type(Oid funcid, if (resolve_polymorphic_tupdesc(tupdesc, &procform->proargtypes, - call_expr)) + call_expr) || + funcid == GttStatisticFunctionId) { if (tupdesc->tdtypeid == RECORDOID && tupdesc->tdtypmod < 0) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index ec3e2c63b0..4c15822d5c 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -15635,8 +15635,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) tbinfo->dobj.catId.oid, false); appendPQExpBuffer(q, "CREATE %s%s %s", - tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ? - "UNLOGGED " : "", + tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ? "UNLOGGED " + : tbinfo->relpersistence == RELPERSISTENCE_SESSION ? "SESSION " : "", reltypename, qualrelname); diff --git a/src/common/relpath.c b/src/common/relpath.c index ad733d1363..be38d1728b 100644 --- a/src/common/relpath.c +++ b/src/common/relpath.c @@ -169,7 +169,18 @@ GetRelationPath(Oid dbNode, Oid spcNode, Oid relNode, } else { - if (forkNumber != MAIN_FORKNUM) + /* + * Session relations are distinguished from local temp relations by adding + * SessionRelFirstBackendId offset to backendId. + * These is no need to separate them at file system level, so just subtract SessionRelFirstBackendId + * to avoid too long file names. + * Segments of session relations have the same prefix (t%d_) as local temporary relations + * to make it possible to cleanup them in the same way as local temporary relation files. + */ + if (backendId >= SessionRelFirstBackendId) + backendId -= SessionRelFirstBackendId; + + if (forkNumber != MAIN_FORKNUM) path = psprintf("base/%u/t%d_%u_%s", dbNode, backendId, relNode, forkNames[forkNumber]); diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index a12fc1fc46..89c3645c39 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -165,6 +165,7 @@ typedef FormData_pg_class *Form_pg_class; #define RELPERSISTENCE_PERMANENT 'p' /* regular table */ #define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */ #define RELPERSISTENCE_TEMP 't' /* temporary table */ +#define RELPERSISTENCE_SESSION 's' /* session table */ /* default selection for replica identity (primary key or nothing) */ #define REPLICA_IDENTITY_DEFAULT 'd' diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 2228256907..6757491d35 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5503,7 +5503,14 @@ proname => 'pg_stat_get_xact_function_self_time', provolatile => 'v', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_xact_function_self_time' }, - +{ oid => '3434', + descr => 'show local statistics for global temp table', + proname => 'pg_gtt_statistic_for_relation', provolatile => 'v', proparallel => 'u', + prorettype => 'record', proretset => 't', prorows => '100', proargtypes => 'oid', + proallargtypes => '{oid,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}', + proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', + proargnames => '{relid,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}', + prosrc => 'pg_gtt_statistic_for_relation' }, { oid => '3788', descr => 'statistics: timestamp of the current statistics snapshot', proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's', diff --git a/src/include/storage/backendid.h b/src/include/storage/backendid.h index 0c776a3e6c..124fc3c8fb 100644 --- a/src/include/storage/backendid.h +++ b/src/include/storage/backendid.h @@ -22,6 +22,13 @@ typedef int BackendId; /* unique currently active backend identifier */ #define InvalidBackendId (-1) +/* + * We need to distinguish local and global temporary relations by RelFileNodeBackend. + * The least invasive change is to add some special bias value to backend id (since + * maximal number of backed is limited by MaxBackends). + */ +#define SessionRelFirstBackendId (0x40000000) + extern PGDLLIMPORT BackendId MyBackendId; /* backend id of this backend */ /* backend id of our parallel session leader, or InvalidBackendId if none */ @@ -34,4 +41,12 @@ extern PGDLLIMPORT BackendId ParallelMasterBackendId; #define BackendIdForTempRelations() \ (ParallelMasterBackendId == InvalidBackendId ? MyBackendId : ParallelMasterBackendId) + +#define BackendIdForSessionRelations() \ + (BackendIdForTempRelations() + SessionRelFirstBackendId) + +#define IsSessionRelationBackendId(id) ((id) >= SessionRelFirstBackendId) + +#define GetRelationBackendId(id) ((id) & ~SessionRelFirstBackendId) + #endif /* BACKENDID_H */ diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h index 3f88683a05..7ecef10e41 100644 --- a/src/include/storage/bufpage.h +++ b/src/include/storage/bufpage.h @@ -228,6 +228,13 @@ typedef PageHeaderData *PageHeader; */ #define PageIsNew(page) (((PageHeader) (page))->pd_upper == 0) +/* + * Page of temporary relation is not initialized + */ +#define GlobalTempRelationPageIsNotInitialized(rel, page) \ + ((rel)->rd_rel->relpersistence == RELPERSISTENCE_SESSION && PageIsNew(page)) + + /* * PageGetItemId * Returns an item identifier of a page. diff --git a/src/include/storage/md.h b/src/include/storage/md.h index ec7630ce3b..56838831b7 100644 --- a/src/include/storage/md.h +++ b/src/include/storage/md.h @@ -31,7 +31,7 @@ extern void mdextend(SMgrRelation reln, ForkNumber forknum, extern void mdprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); extern void mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, - char *buffer); + char *buffer, bool skipInit); extern void mdwrite(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer, bool skipFsync); extern void mdwriteback(SMgrRelation reln, ForkNumber forknum, diff --git a/src/include/storage/relfilenode.h b/src/include/storage/relfilenode.h index 4de9fc1e69..c45040b768 100644 --- a/src/include/storage/relfilenode.h +++ b/src/include/storage/relfilenode.h @@ -75,9 +75,24 @@ typedef struct RelFileNodeBackend BackendId backend; } RelFileNodeBackend; +/* + * Check whether it is local or global temporary relation, which data belongs only to one backend. + */ #define RelFileNodeBackendIsTemp(rnode) \ ((rnode).backend != InvalidBackendId) +/* + * Check whether it is global temporary relation which metadata is shared by all sessions, + * but data is private for the current session. + */ +#define RelFileNodeBackendIsGlobalTemp(rnode) IsSessionRelationBackendId((rnode).backend) + +/* + * Check whether it is local temporary relation which exists only in this backend. + */ +#define RelFileNodeBackendIsLocalTemp(rnode) \ + (RelFileNodeBackendIsTemp(rnode) && !RelFileNodeBackendIsGlobalTemp(rnode)) + /* * Note: RelFileNodeEquals and RelFileNodeBackendEquals compare relNode first * since that is most likely to be different in two unequal RelFileNodes. It diff --git a/src/include/storage/smgr.h b/src/include/storage/smgr.h index 243822137c..a4a2da2e0b 100644 --- a/src/include/storage/smgr.h +++ b/src/include/storage/smgr.h @@ -95,7 +95,7 @@ extern void smgrextend(SMgrRelation reln, ForkNumber forknum, extern void smgrprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); extern void smgrread(SMgrRelation reln, ForkNumber forknum, - BlockNumber blocknum, char *buffer); + BlockNumber blocknum, char *buffer, bool skipInit); extern void smgrwrite(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer, bool skipFsync); extern void smgrwriteback(SMgrRelation reln, ForkNumber forknum, diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h index f4aa316604..365b02a9ba 100644 --- a/src/include/utils/catcache.h +++ b/src/include/utils/catcache.h @@ -228,4 +228,8 @@ extern void PrepareToInvalidateCacheTuple(Relation relation, extern void PrintCatCacheLeakWarning(HeapTuple tuple); extern void PrintCatCacheListLeakWarning(CatCList *list); +extern void InsertCatCache(CatCache *cache, + Datum v1, Datum v2, Datum v3, Datum v4, + HeapTuple tuple); + #endif /* CATCACHE_H */ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 44ed04dd3f..ae56427cba 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -277,6 +277,7 @@ typedef struct StdRdOptions int parallel_workers; /* max number of parallel workers */ bool vacuum_index_cleanup; /* enables index vacuuming and cleanup */ bool vacuum_truncate; /* enables vacuum to truncate a relation */ + bool on_commit_delete_rows; /* global temp table */ } StdRdOptions; #define HEAP_MIN_FILLFACTOR 10 @@ -332,6 +333,18 @@ typedef struct StdRdOptions ((relation)->rd_options ? \ ((StdRdOptions *) (relation)->rd_options)->parallel_workers : (defaultpw)) +/* + * Relation persistence is either TEMP either SESSION + */ +#define IsLocalRelpersistence(relpersistence) \ + ((relpersistence) == RELPERSISTENCE_TEMP || (relpersistence) == RELPERSISTENCE_SESSION) + +/* + * Relation is either global either local temp table + */ +#define RelationHasSessionScope(relation) \ + IsLocalRelpersistence(((relation)->rd_rel->relpersistence)) + /* ViewOptions->check_option values */ typedef enum ViewOptCheckOption { @@ -340,6 +353,7 @@ typedef enum ViewOptCheckOption VIEW_OPTION_CHECK_OPTION_CASCADED } ViewOptCheckOption; + /* * ViewOptions * Contents of rd_options for views @@ -535,7 +549,7 @@ typedef struct ViewOptions * True if relation's pages are stored in local buffers. */ #define RelationUsesLocalBuffers(relation) \ - ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + RelationHasSessionScope(relation) /* * RELATION_IS_LOCAL diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h index f27b73d76d..eaf21d987d 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -216,4 +216,8 @@ extern bool RelationSupportsSysCache(Oid relid); #define ReleaseSysCacheList(x) ReleaseCatCacheList(x) + +extern void InsertSysCache(int cacheId, + Datum v1, Datum v2, Datum v3, Datum v4, + HeapTuple tuple); #endif /* SYSCACHE_H */ diff --git a/src/test/isolation/expected/inherit-global-temp.out b/src/test/isolation/expected/inherit-global-temp.out new file mode 100644 index 0000000000..6114f8c091 --- /dev/null +++ b/src/test/isolation/expected/inherit-global-temp.out @@ -0,0 +1,218 @@ +Parsed test spec with 2 sessions + +starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_select_p s1_select_c s2_select_p s2_select_c +step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2); +step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4); +step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6); +step s1_select_p: SELECT a FROM inh_global_parent; +a + +1 +2 +3 +4 +step s1_select_c: SELECT a FROM inh_global_temp_child_s1; +a + +3 +4 +step s2_select_p: SELECT a FROM inh_global_parent; +a + +1 +2 +5 +6 +step s2_select_c: SELECT a FROM inh_global_temp_child_s2; +a + +5 +6 + +starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_update_p s1_update_c s1_select_p s1_select_c s2_select_p s2_select_c +step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2); +step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4); +step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6); +step s1_update_p: UPDATE inh_global_parent SET a = 11 WHERE a = 1; +step s1_update_c: UPDATE inh_global_parent SET a = 13 WHERE a IN (3, 5); +step s1_select_p: SELECT a FROM inh_global_parent; +a + +2 +11 +4 +13 +step s1_select_c: SELECT a FROM inh_global_temp_child_s1; +a + +4 +13 +step s2_select_p: SELECT a FROM inh_global_parent; +a + +2 +11 +5 +6 +step s2_select_c: SELECT a FROM inh_global_temp_child_s2; +a + +5 +6 + +starting permutation: s1_insert_p s1_insert_c s2_insert_c s2_update_c s1_select_p s1_select_c s2_select_p s2_select_c +step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2); +step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4); +step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6); +step s2_update_c: UPDATE inh_global_parent SET a = 15 WHERE a IN (3, 5); +step s1_select_p: SELECT a FROM inh_global_parent; +a + +1 +2 +3 +4 +step s1_select_c: SELECT a FROM inh_global_temp_child_s1; +a + +3 +4 +step s2_select_p: SELECT a FROM inh_global_parent; +a + +1 +2 +6 +15 +step s2_select_c: SELECT a FROM inh_global_temp_child_s2; +a + +6 +15 + +starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_delete_p s1_delete_c s1_select_p s1_select_c s2_select_p s2_select_c +step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2); +step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4); +step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6); +step s1_delete_p: DELETE FROM inh_global_parent WHERE a = 2; +step s1_delete_c: DELETE FROM inh_global_parent WHERE a IN (4, 6); +step s1_select_p: SELECT a FROM inh_global_parent; +a + +1 +3 +step s1_select_c: SELECT a FROM inh_global_temp_child_s1; +a + +3 +step s2_select_p: SELECT a FROM inh_global_parent; +a + +1 +5 +6 +step s2_select_c: SELECT a FROM inh_global_temp_child_s2; +a + +5 +6 + +starting permutation: s1_insert_p s1_insert_c s2_insert_c s2_delete_c s1_select_p s1_select_c s2_select_p s2_select_c +step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2); +step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4); +step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6); +step s2_delete_c: DELETE FROM inh_global_parent WHERE a IN (4, 6); +step s1_select_p: SELECT a FROM inh_global_parent; +a + +1 +2 +3 +4 +step s1_select_c: SELECT a FROM inh_global_temp_child_s1; +a + +3 +4 +step s2_select_p: SELECT a FROM inh_global_parent; +a + +1 +2 +5 +step s2_select_c: SELECT a FROM inh_global_temp_child_s2; +a + +5 + +starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_truncate_p s1_select_p s1_select_c s2_select_p s2_select_c +step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2); +step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4); +step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6); +step s1_truncate_p: TRUNCATE inh_global_parent; +step s1_select_p: SELECT a FROM inh_global_parent; +a + +step s1_select_c: SELECT a FROM inh_global_temp_child_s1; +a + +step s2_select_p: SELECT a FROM inh_global_parent; +a + +5 +6 +step s2_select_c: SELECT a FROM inh_global_temp_child_s2; +a + +5 +6 + +starting permutation: s1_insert_p s1_insert_c s2_insert_c s2_truncate_p s1_select_p s1_select_c s2_select_p s2_select_c +step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2); +step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4); +step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6); +step s2_truncate_p: TRUNCATE inh_global_parent; +step s1_select_p: SELECT a FROM inh_global_parent; +a + +3 +4 +step s1_select_c: SELECT a FROM inh_global_temp_child_s1; +a + +3 +4 +step s2_select_p: SELECT a FROM inh_global_parent; +a + +step s2_select_c: SELECT a FROM inh_global_temp_child_s2; +a + + +starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_begin s1_truncate_p s2_select_p s1_commit +step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2); +step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4); +step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6); +step s1_begin: BEGIN; +step s1_truncate_p: TRUNCATE inh_global_parent; +step s2_select_p: SELECT a FROM inh_global_parent; <waiting ...> +step s1_commit: COMMIT; +step s2_select_p: <... completed> +a + +5 +6 + +starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_begin s1_truncate_p s2_select_c s1_commit +step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2); +step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4); +step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6); +step s1_begin: BEGIN; +step s1_truncate_p: TRUNCATE inh_global_parent; +step s2_select_c: SELECT a FROM inh_global_temp_child_s2; <waiting ...> +step s1_commit: COMMIT; +step s2_select_c: <... completed> +a + +5 +6 diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule index a2fa19230d..ef7aa85706 100644 --- a/src/test/isolation/isolation_schedule +++ b/src/test/isolation/isolation_schedule @@ -88,3 +88,4 @@ test: plpgsql-toast test: truncate-conflict test: serializable-parallel test: serializable-parallel-2 +test: inherit-global-temp diff --git a/src/test/isolation/specs/inherit-global-temp.spec b/src/test/isolation/specs/inherit-global-temp.spec new file mode 100644 index 0000000000..5e95dd6f85 --- /dev/null +++ b/src/test/isolation/specs/inherit-global-temp.spec @@ -0,0 +1,73 @@ +# This is a copy of the inherit-temp test with little changes for global temporary tables. +# + +setup +{ + CREATE TABLE inh_global_parent (a int); +} + +teardown +{ + DROP TABLE inh_global_parent; +} + +# Session 1 executes actions which act directly on both the parent and +# its child. Abbreviation "c" is used for queries working on the child +# and "p" on the parent. +session "s1" +setup +{ + CREATE GLOBAL TEMPORARY TABLE inh_global_temp_child_s1 () INHERITS (inh_global_parent); +} +step "s1_begin" { BEGIN; } +step "s1_truncate_p" { TRUNCATE inh_global_parent; } +step "s1_select_p" { SELECT a FROM inh_global_parent; } +step "s1_select_c" { SELECT a FROM inh_global_temp_child_s1; } +step "s1_insert_p" { INSERT INTO inh_global_parent VALUES (1), (2); } +step "s1_insert_c" { INSERT INTO inh_global_temp_child_s1 VALUES (3), (4); } +step "s1_update_p" { UPDATE inh_global_parent SET a = 11 WHERE a = 1; } +step "s1_update_c" { UPDATE inh_global_parent SET a = 13 WHERE a IN (3, 5); } +step "s1_delete_p" { DELETE FROM inh_global_parent WHERE a = 2; } +step "s1_delete_c" { DELETE FROM inh_global_parent WHERE a IN (4, 6); } +step "s1_commit" { COMMIT; } +teardown +{ + DROP TABLE inh_global_temp_child_s1; +} + +# Session 2 executes actions on the parent which act only on the child. +session "s2" +setup +{ + CREATE GLOBAL TEMPORARY TABLE inh_global_temp_child_s2 () INHERITS (inh_global_parent); +} +step "s2_truncate_p" { TRUNCATE inh_global_parent; } +step "s2_select_p" { SELECT a FROM inh_global_parent; } +step "s2_select_c" { SELECT a FROM inh_global_temp_child_s2; } +step "s2_insert_c" { INSERT INTO inh_global_temp_child_s2 VALUES (5), (6); } +step "s2_update_c" { UPDATE inh_global_parent SET a = 15 WHERE a IN (3, 5); } +step "s2_delete_c" { DELETE FROM inh_global_parent WHERE a IN (4, 6); } +teardown +{ + DROP TABLE inh_global_temp_child_s2; +} + +# Check INSERT behavior across sessions +permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c" + +# Check UPDATE behavior across sessions +permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_update_p" "s1_update_c" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c" +permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s2_update_c" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c" + +# Check DELETE behavior across sessions +permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_delete_p" "s1_delete_c" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c" +permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s2_delete_c" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c" + +# Check TRUNCATE behavior across sessions +permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_truncate_p" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c" +permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s2_truncate_p" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c" + +# TRUNCATE on a parent tree does not block access to temporary child relation +# of another session, and blocks when scanning the parent. +permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_begin" "s1_truncate_p" "s2_select_p" "s1_commit" +permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_begin" "s1_truncate_p" "s2_select_c" "s1_commit" diff --git a/src/test/regress/expected/global_temp.out b/src/test/regress/expected/global_temp.out new file mode 100644 index 0000000000..ae1adb6673 --- /dev/null +++ b/src/test/regress/expected/global_temp.out @@ -0,0 +1,247 @@ +-- +-- GLOBAL TEMP +-- Test global temp relations +-- +-- Test ON COMMIT DELETE ROWS +CREATE GLOBAL TEMP TABLE global_temptest(col int) ON COMMIT DELETE ROWS; +BEGIN; +INSERT INTO global_temptest VALUES (1); +INSERT INTO global_temptest VALUES (2); +SELECT * FROM global_temptest; + col +----- + 1 + 2 +(2 rows) + +COMMIT; +SELECT * FROM global_temptest; + col +----- +(0 rows) + +DROP TABLE global_temptest; +BEGIN; +CREATE GLOBAL TEMP TABLE global_temptest(col) ON COMMIT DELETE ROWS AS SELECT 1; +SELECT * FROM global_temptest; + col +----- + 1 +(1 row) + +COMMIT; +SELECT * FROM global_temptest; + col +----- +(0 rows) + +DROP TABLE global_temptest; +-- Test foreign keys +BEGIN; +CREATE GLOBAL TEMP TABLE global_temptest1(col int PRIMARY KEY); +CREATE GLOBAL TEMP TABLE global_temptest2(col int REFERENCES global_temptest1) + ON COMMIT DELETE ROWS; +INSERT INTO global_temptest1 VALUES (1); +INSERT INTO global_temptest2 VALUES (1); +COMMIT; +SELECT * FROM global_temptest1; + col +----- + 1 +(1 row) + +SELECT * FROM global_temptest2; + col +----- +(0 rows) + +BEGIN; +CREATE GLOBAL TEMP TABLE global_temptest3(col int PRIMARY KEY) ON COMMIT DELETE ROWS; +CREATE GLOBAL TEMP TABLE global_temptest4(col int REFERENCES global_temptest3); +COMMIT; +ERROR: unsupported ON COMMIT and foreign key combination +DETAIL: Table "global_temptest4" references "global_temptest3", but they do not have the same ON COMMIT setting. +-- For partitioned temp tables, ON COMMIT actions ignore storage-less +-- partitioned tables. +BEGIN; +CREATE GLOBAL TEMP TABLE temp_parted_oncommit (a int) + PARTITION BY LIST (a) ON COMMIT DELETE ROWS; +CREATE GLOBAL TEMP TABLE temp_parted_oncommit_1 + PARTITION OF temp_parted_oncommit + FOR VALUES IN (1) ON COMMIT DELETE ROWS; +INSERT INTO temp_parted_oncommit VALUES (1); +COMMIT; +-- partitions are emptied by the previous commit +SELECT * FROM temp_parted_oncommit; + a +--- +(0 rows) + +DROP TABLE temp_parted_oncommit; +-- Using ON COMMIT DELETE on a partitioned table does not remove +-- all rows if partitions preserve their data. +BEGIN; +CREATE GLOBAL TEMP TABLE global_temp_parted_oncommit_test (a int) + PARTITION BY LIST (a) ON COMMIT DELETE ROWS; +CREATE GLOBAL TEMP TABLE global_temp_parted_oncommit_test1 + PARTITION OF global_temp_parted_oncommit_test + FOR VALUES IN (1) ON COMMIT PRESERVE ROWS; +INSERT INTO global_temp_parted_oncommit_test VALUES (1); +COMMIT; +-- Data from the remaining partition is still here as its rows are +-- preserved. +SELECT * FROM global_temp_parted_oncommit_test; + a +--- + 1 +(1 row) + +-- two relations remain in this case. +SELECT relname FROM pg_class WHERE relname LIKE 'global_temp_parted_oncommit_test%'; + relname +----------------------------------- + global_temp_parted_oncommit_test + global_temp_parted_oncommit_test1 +(2 rows) + +DROP TABLE global_temp_parted_oncommit_test; +-- Check dependencies between ON COMMIT actions with inheritance trees. +-- Data on the parent is removed, and the child goes away. +BEGIN; +CREATE GLOBAL TEMP TABLE global_temp_inh_oncommit_test (a int) ON COMMIT DELETE ROWS; +CREATE GLOBAL TEMP TABLE global_temp_inh_oncommit_test1 () + INHERITS(global_temp_inh_oncommit_test) ON COMMIT PRESERVE ROWS; +INSERT INTO global_temp_inh_oncommit_test1 VALUES (1); +INSERT INTO global_temp_inh_oncommit_test VALUES (1); +COMMIT; +SELECT * FROM global_temp_inh_oncommit_test; + a +--- + 1 +(1 row) + +-- two relations remain +SELECT relname FROM pg_class WHERE relname LIKE 'global_temp_inh_oncommit_test%'; + relname +-------------------------------- + global_temp_inh_oncommit_test + global_temp_inh_oncommit_test1 +(2 rows) + +DROP TABLE global_temp_inh_oncommit_test1; +DROP TABLE global_temp_inh_oncommit_test; +-- Global temp table cannot inherit from temporary relation +BEGIN; +CREATE TEMP TABLE global_temp_table (a int) ON COMMIT DELETE ROWS; +CREATE GLOBAL TEMP TABLE global_temp_table1 () + INHERITS(global_temp_table) ON COMMIT PRESERVE ROWS; +ERROR: cannot inherit from temporary relation "global_temp_table" +ROLLBACK; +-- Temp table can inherit from global temporary relation +BEGIN; +CREATE GLOBAL TEMP TABLE global_temp_table (a int) ON COMMIT DELETE ROWS; +CREATE TEMP TABLE temp_table1 () + INHERITS(global_temp_table) ON COMMIT PRESERVE ROWS; +CREATE TEMP TABLE temp_table2 () + INHERITS(global_temp_table) ON COMMIT DELETE ROWS; +INSERT INTO temp_table2 VALUES (2); +INSERT INTO temp_table1 VALUES (1); +INSERT INTO global_temp_table VALUES (0); +SELECT * FROM global_temp_table; + a +--- + 0 + 1 + 2 +(3 rows) + +COMMIT; +SELECT * FROM global_temp_table; + a +--- + 1 +(1 row) + +DROP TABLE temp_table2; +DROP TABLE temp_table1; +DROP TABLE global_temp_table; +-- Global temp table can inherit from normal relation +BEGIN; +CREATE TABLE normal_table (a int); +CREATE GLOBAL TEMP TABLE temp_table1 () + INHERITS(normal_table) ON COMMIT PRESERVE ROWS; +CREATE GLOBAL TEMP TABLE temp_table2 () + INHERITS(normal_table) ON COMMIT DELETE ROWS; +INSERT INTO temp_table2 VALUES (2); +INSERT INTO temp_table1 VALUES (1); +INSERT INTO normal_table VALUES (0); +SELECT * FROM normal_table; + a +--- + 0 + 1 + 2 +(3 rows) + +COMMIT; +SELECT * FROM normal_table; + a +--- + 0 + 1 +(2 rows) + +DROP TABLE temp_table2; +DROP TABLE temp_table1; +DROP TABLE normal_table; +-- Check SERIAL and BIGSERIAL pseudo-types +CREATE GLOBAL TEMP TABLE global_temp_table ( aid BIGSERIAL, bid SERIAL ); +CREATE SEQUENCE test_sequence; +INSERT INTO global_temp_table DEFAULT VALUES; +INSERT INTO global_temp_table DEFAULT VALUES; +INSERT INTO global_temp_table DEFAULT VALUES; +SELECT * FROM global_temp_table; + aid | bid +-----+----- + 1 | 1 + 2 | 2 + 3 | 3 +(3 rows) + +SELECT NEXTVAL( 'test_sequence' ); + nextval +--------- + 1 +(1 row) + +\c +SELECT * FROM global_temp_table; + aid | bid +-----+----- +(0 rows) + +SELECT NEXTVAL( 'test_sequence' ); + nextval +--------- + 2 +(1 row) + +INSERT INTO global_temp_table DEFAULT VALUES; +INSERT INTO global_temp_table DEFAULT VALUES; +INSERT INTO global_temp_table DEFAULT VALUES; +SELECT * FROM global_temp_table; + aid | bid +-----+----- + 1 | 1 + 2 | 2 + 3 | 3 +(3 rows) + +SELECT NEXTVAL( 'test_sequence' ); + nextval +--------- + 3 +(1 row) + +DROP TABLE global_temp_table; +DROP SEQUENCE test_sequence; diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2ab2115fa1..7538601870 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1349,6 +1349,40 @@ pg_group| SELECT pg_authid.rolname AS groname, WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist FROM pg_authid WHERE (NOT pg_authid.rolcanlogin); +pg_gtt_statistic| SELECT s.starelid, + s.staattnum, + s.stainherit, + s.stanullfrac, + s.stawidth, + s.stadistinct, + s.stakind1, + s.stakind2, + s.stakind3, + s.stakind4, + s.stakind5, + s.staop1, + s.staop2, + s.staop3, + s.staop4, + s.staop5, + s.stacoll1, + s.stacoll2, + s.stacoll3, + s.stacoll4, + s.stacoll5, + s.stanumbers1, + s.stanumbers2, + s.stanumbers3, + s.stanumbers4, + s.stanumbers5, + s.stavalues1, + s.stavalues2, + s.stavalues3, + s.stavalues4, + s.stavalues5 + FROM pg_class c, + LATERAL pg_gtt_statistic_for_relation(c.oid) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5) + WHERE (c.relpersistence = 's'::"char"); pg_hba_file_rules| SELECT a.line_number, a.type, a.database, diff --git a/src/test/regress/expected/session_table.out b/src/test/regress/expected/session_table.out new file mode 100644 index 0000000000..1b9b3f4d20 --- /dev/null +++ b/src/test/regress/expected/session_table.out @@ -0,0 +1,64 @@ +create session table my_private_table(x integer primary key, y integer); +insert into my_private_table values (generate_series(1,10000), generate_series(1,10000)); +select count(*) from my_private_table; + count +------- + 10000 +(1 row) + +\c +select count(*) from my_private_table; + count +------- + 0 +(1 row) + +select * from my_private_table where x=10001; + x | y +---+--- +(0 rows) + +insert into my_private_table values (generate_series(1,100000), generate_series(1,100000)); +create index on my_private_table(y); +select * from my_private_table where x=10001; + x | y +-------+------- + 10001 | 10001 +(1 row) + +select * from my_private_table where y=10001; + x | y +-------+------- + 10001 | 10001 +(1 row) + +select count(*) from my_private_table; + count +-------- + 100000 +(1 row) + +\c +select * from my_private_table where x=100001; + x | y +---+--- +(0 rows) + +select * from my_private_table order by y desc limit 1; + x | y +---+--- +(0 rows) + +insert into my_private_table values (generate_series(1,100000), generate_series(1,100000)); +select * from my_private_table where x=100001; + x | y +---+--- +(0 rows) + +select * from my_private_table order by y desc limit 1; + x | y +--------+-------- + 100000 | 100000 +(1 row) + +drop table my_private_table; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index d2b17dd3ea..71c8ca4f20 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -107,7 +107,7 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath # NB: temp.sql does a reconnect which transiently uses 2 connections, # so keep this parallel group to at most 19 tests # ---------- -test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml +test: plancache limit plpgsql copy2 temp global_temp session_table domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml # ---------- # Another group of parallel tests diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index acba391332..71abe08e4e 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -172,6 +172,8 @@ test: limit test: plpgsql test: copy2 test: temp +test: global_temp +test: session_table test: domain test: rangefuncs test: prepare diff --git a/src/test/regress/sql/global_temp.sql b/src/test/regress/sql/global_temp.sql new file mode 100644 index 0000000000..3058b9b2c1 --- /dev/null +++ b/src/test/regress/sql/global_temp.sql @@ -0,0 +1,151 @@ +-- +-- GLOBAL TEMP +-- Test global temp relations +-- + +-- Test ON COMMIT DELETE ROWS + +CREATE GLOBAL TEMP TABLE global_temptest(col int) ON COMMIT DELETE ROWS; + +BEGIN; +INSERT INTO global_temptest VALUES (1); +INSERT INTO global_temptest VALUES (2); + +SELECT * FROM global_temptest; +COMMIT; + +SELECT * FROM global_temptest; + +DROP TABLE global_temptest; + +BEGIN; +CREATE GLOBAL TEMP TABLE global_temptest(col) ON COMMIT DELETE ROWS AS SELECT 1; + +SELECT * FROM global_temptest; +COMMIT; + +SELECT * FROM global_temptest; + +DROP TABLE global_temptest; + +-- Test foreign keys +BEGIN; +CREATE GLOBAL TEMP TABLE global_temptest1(col int PRIMARY KEY); +CREATE GLOBAL TEMP TABLE global_temptest2(col int REFERENCES global_temptest1) + ON COMMIT DELETE ROWS; +INSERT INTO global_temptest1 VALUES (1); +INSERT INTO global_temptest2 VALUES (1); +COMMIT; +SELECT * FROM global_temptest1; +SELECT * FROM global_temptest2; + +BEGIN; +CREATE GLOBAL TEMP TABLE global_temptest3(col int PRIMARY KEY) ON COMMIT DELETE ROWS; +CREATE GLOBAL TEMP TABLE global_temptest4(col int REFERENCES global_temptest3); +COMMIT; + +-- For partitioned temp tables, ON COMMIT actions ignore storage-less +-- partitioned tables. +BEGIN; +CREATE GLOBAL TEMP TABLE temp_parted_oncommit (a int) + PARTITION BY LIST (a) ON COMMIT DELETE ROWS; +CREATE GLOBAL TEMP TABLE temp_parted_oncommit_1 + PARTITION OF temp_parted_oncommit + FOR VALUES IN (1) ON COMMIT DELETE ROWS; +INSERT INTO temp_parted_oncommit VALUES (1); +COMMIT; +-- partitions are emptied by the previous commit +SELECT * FROM temp_parted_oncommit; +DROP TABLE temp_parted_oncommit; + +-- Using ON COMMIT DELETE on a partitioned table does not remove +-- all rows if partitions preserve their data. +BEGIN; +CREATE GLOBAL TEMP TABLE global_temp_parted_oncommit_test (a int) + PARTITION BY LIST (a) ON COMMIT DELETE ROWS; +CREATE GLOBAL TEMP TABLE global_temp_parted_oncommit_test1 + PARTITION OF global_temp_parted_oncommit_test + FOR VALUES IN (1) ON COMMIT PRESERVE ROWS; +INSERT INTO global_temp_parted_oncommit_test VALUES (1); +COMMIT; +-- Data from the remaining partition is still here as its rows are +-- preserved. +SELECT * FROM global_temp_parted_oncommit_test; +-- two relations remain in this case. +SELECT relname FROM pg_class WHERE relname LIKE 'global_temp_parted_oncommit_test%'; +DROP TABLE global_temp_parted_oncommit_test; + +-- Check dependencies between ON COMMIT actions with inheritance trees. +-- Data on the parent is removed, and the child goes away. +BEGIN; +CREATE GLOBAL TEMP TABLE global_temp_inh_oncommit_test (a int) ON COMMIT DELETE ROWS; +CREATE GLOBAL TEMP TABLE global_temp_inh_oncommit_test1 () + INHERITS(global_temp_inh_oncommit_test) ON COMMIT PRESERVE ROWS; +INSERT INTO global_temp_inh_oncommit_test1 VALUES (1); +INSERT INTO global_temp_inh_oncommit_test VALUES (1); +COMMIT; +SELECT * FROM global_temp_inh_oncommit_test; +-- two relations remain +SELECT relname FROM pg_class WHERE relname LIKE 'global_temp_inh_oncommit_test%'; +DROP TABLE global_temp_inh_oncommit_test1; +DROP TABLE global_temp_inh_oncommit_test; + +-- Global temp table cannot inherit from temporary relation +BEGIN; +CREATE TEMP TABLE global_temp_table (a int) ON COMMIT DELETE ROWS; +CREATE GLOBAL TEMP TABLE global_temp_table1 () + INHERITS(global_temp_table) ON COMMIT PRESERVE ROWS; +ROLLBACK; + +-- Temp table can inherit from global temporary relation +BEGIN; +CREATE GLOBAL TEMP TABLE global_temp_table (a int) ON COMMIT DELETE ROWS; +CREATE TEMP TABLE temp_table1 () + INHERITS(global_temp_table) ON COMMIT PRESERVE ROWS; +CREATE TEMP TABLE temp_table2 () + INHERITS(global_temp_table) ON COMMIT DELETE ROWS; +INSERT INTO temp_table2 VALUES (2); +INSERT INTO temp_table1 VALUES (1); +INSERT INTO global_temp_table VALUES (0); +SELECT * FROM global_temp_table; +COMMIT; +SELECT * FROM global_temp_table; +DROP TABLE temp_table2; +DROP TABLE temp_table1; +DROP TABLE global_temp_table; + +-- Global temp table can inherit from normal relation +BEGIN; +CREATE TABLE normal_table (a int); +CREATE GLOBAL TEMP TABLE temp_table1 () + INHERITS(normal_table) ON COMMIT PRESERVE ROWS; +CREATE GLOBAL TEMP TABLE temp_table2 () + INHERITS(normal_table) ON COMMIT DELETE ROWS; +INSERT INTO temp_table2 VALUES (2); +INSERT INTO temp_table1 VALUES (1); +INSERT INTO normal_table VALUES (0); +SELECT * FROM normal_table; +COMMIT; +SELECT * FROM normal_table; +DROP TABLE temp_table2; +DROP TABLE temp_table1; +DROP TABLE normal_table; + +-- Check SERIAL and BIGSERIAL pseudo-types +CREATE GLOBAL TEMP TABLE global_temp_table ( aid BIGSERIAL, bid SERIAL ); +CREATE SEQUENCE test_sequence; +INSERT INTO global_temp_table DEFAULT VALUES; +INSERT INTO global_temp_table DEFAULT VALUES; +INSERT INTO global_temp_table DEFAULT VALUES; +SELECT * FROM global_temp_table; +SELECT NEXTVAL( 'test_sequence' ); +\c +SELECT * FROM global_temp_table; +SELECT NEXTVAL( 'test_sequence' ); +INSERT INTO global_temp_table DEFAULT VALUES; +INSERT INTO global_temp_table DEFAULT VALUES; +INSERT INTO global_temp_table DEFAULT VALUES; +SELECT * FROM global_temp_table; +SELECT NEXTVAL( 'test_sequence' ); +DROP TABLE global_temp_table; +DROP SEQUENCE test_sequence; diff --git a/src/test/regress/sql/session_table.sql b/src/test/regress/sql/session_table.sql new file mode 100644 index 0000000000..c6663dc89b --- /dev/null +++ b/src/test/regress/sql/session_table.sql @@ -0,0 +1,18 @@ +create session table my_private_table(x integer primary key, y integer); +insert into my_private_table values (generate_series(1,10000), generate_series(1,10000)); +select count(*) from my_private_table; +\c +select count(*) from my_private_table; +select * from my_private_table where x=10001; +insert into my_private_table values (generate_series(1,100000), generate_series(1,100000)); +create index on my_private_table(y); +select * from my_private_table where x=10001; +select * from my_private_table where y=10001; +select count(*) from my_private_table; +\c +select * from my_private_table where x=100001; +select * from my_private_table order by y desc limit 1; +insert into my_private_table values (generate_series(1,100000), generate_series(1,100000)); +select * from my_private_table where x=100001; +select * from my_private_table order by y desc limit 1; +drop table my_private_table;