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