public inbox for [email protected]help / color / mirror / Atom feed
[PATCH v14 1/2] In-place table persistence change 3+ messages / 2 participants [nested] [flat]
* [PATCH v14 1/2] In-place table persistence change @ 2020-11-11 12:51 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 3+ messages in thread From: Kyotaro Horiguchi @ 2020-11-11 12:51 UTC (permalink / raw) Even though ALTER TABLE SET LOGGED/UNLOGGED does not require data rewriting, currently it runs heap rewrite which causes large amount of file I/O. This patch makes the command run without heap rewrite. Addition to that, SET LOGGED while wal_level > minimal emits WAL using XLOG_FPI instead of massive number of HEAP_INSERT's, which should be smaller. Also this allows for the cleanup of files left behind in the crash of the transaction that created it. --- src/backend/access/rmgrdesc/smgrdesc.c | 52 ++ src/backend/access/transam/README | 8 + src/backend/access/transam/xact.c | 7 + src/backend/access/transam/xlog.c | 17 + src/backend/catalog/storage.c | 545 +++++++++++++++++- src/backend/commands/tablecmds.c | 266 +++++++-- src/backend/replication/basebackup.c | 3 +- src/backend/storage/buffer/bufmgr.c | 88 +++ src/backend/storage/file/fd.c | 4 +- src/backend/storage/file/reinit.c | 344 +++++++---- src/backend/storage/smgr/md.c | 93 ++- src/backend/storage/smgr/smgr.c | 32 + src/backend/storage/sync/sync.c | 20 +- src/bin/pg_rewind/parsexlog.c | 24 + src/common/relpath.c | 47 +- src/include/catalog/storage.h | 3 + src/include/catalog/storage_xlog.h | 42 +- src/include/common/relpath.h | 9 +- src/include/storage/bufmgr.h | 2 + src/include/storage/fd.h | 1 + src/include/storage/md.h | 8 +- src/include/storage/reinit.h | 10 +- src/include/storage/smgr.h | 17 + src/test/recovery/t/027_persistence_change.pl | 247 ++++++++ 24 files changed, 1707 insertions(+), 182 deletions(-) create mode 100644 src/test/recovery/t/027_persistence_change.pl diff --git a/src/backend/access/rmgrdesc/smgrdesc.c b/src/backend/access/rmgrdesc/smgrdesc.c index 7755553d57..d251f22207 100644 --- a/src/backend/access/rmgrdesc/smgrdesc.c +++ b/src/backend/access/rmgrdesc/smgrdesc.c @@ -40,6 +40,49 @@ smgr_desc(StringInfo buf, XLogReaderState *record) xlrec->blkno, xlrec->flags); pfree(path); } + else if (info == XLOG_SMGR_UNLINK) + { + xl_smgr_unlink *xlrec = (xl_smgr_unlink *) rec; + char *path = relpathperm(xlrec->rnode, xlrec->forkNum); + + appendStringInfoString(buf, path); + pfree(path); + } + else if (info == XLOG_SMGR_MARK) + { + xl_smgr_mark *xlrec = (xl_smgr_mark *) rec; + char *path = GetRelationPath(xlrec->rnode.dbNode, + xlrec->rnode.spcNode, + xlrec->rnode.relNode, + InvalidBackendId, + xlrec->forkNum, xlrec->mark); + char *action; + + switch (xlrec->action) + { + case XLOG_SMGR_MARK_CREATE: + action = "CREATE"; + break; + case XLOG_SMGR_MARK_UNLINK: + action = "DELETE"; + break; + default: + action = "<unknown action>"; + break; + } + + appendStringInfo(buf, "%s %s", action, path); + pfree(path); + } + else if (info == XLOG_SMGR_BUFPERSISTENCE) + { + xl_smgr_bufpersistence *xlrec = (xl_smgr_bufpersistence *) rec; + char *path = relpathperm(xlrec->rnode, MAIN_FORKNUM); + + appendStringInfoString(buf, path); + appendStringInfo(buf, " persistence %d", xlrec->persistence); + pfree(path); + } } const char * @@ -55,6 +98,15 @@ smgr_identify(uint8 info) case XLOG_SMGR_TRUNCATE: id = "TRUNCATE"; break; + case XLOG_SMGR_UNLINK: + id = "UNLINK"; + break; + case XLOG_SMGR_MARK: + id = "MARK"; + break; + case XLOG_SMGR_BUFPERSISTENCE: + id = "BUFPERSISTENCE"; + break; } return id; diff --git a/src/backend/access/transam/README b/src/backend/access/transam/README index 1edc8180c1..b344bbe511 100644 --- a/src/backend/access/transam/README +++ b/src/backend/access/transam/README @@ -724,6 +724,14 @@ we must panic and abort recovery. The DBA will have to manually clean up then restart recovery. This is part of the reason for not writing a WAL entry until we've successfully done the original action. +The Smgr MARK files +-------------------------------- + +An smgr mark file is created when a new relation file is created to +mark the relfilenode needs to be cleaned up at recovery time. In +contrast to the four actions above, failure to remove smgr mark files +will lead to data loss, in which case the server will shut down. + Skipping WAL for New RelFileNode -------------------------------- diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index e7b0bc804d..b41186d6d8 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -2197,6 +2197,9 @@ CommitTransaction(void) */ smgrDoPendingSyncs(true, is_parallel_worker); + /* Likewise delete mark files for files created during this transaction. */ + smgrDoPendingCleanups(true); + /* close large objects before lower-level cleanup */ AtEOXact_LargeObject(true); @@ -2447,6 +2450,9 @@ PrepareTransaction(void) */ smgrDoPendingSyncs(true, false); + /* Likewise delete mark files for files created during this transaction. */ + smgrDoPendingCleanups(true); + /* close large objects before lower-level cleanup */ AtEOXact_LargeObject(true); @@ -2772,6 +2778,7 @@ AbortTransaction(void) AfterTriggerEndXact(false); /* 'false' means it's abort */ AtAbort_Portals(); smgrDoPendingSyncs(false, is_parallel_worker); + smgrDoPendingCleanups(false); AtEOXact_LargeObject(false); AtAbort_Notify(); AtEOXact_RelationMap(false, is_parallel_worker); diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 87cd05c945..243860fcb1 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -40,6 +40,7 @@ #include "catalog/catversion.h" #include "catalog/pg_control.h" #include "catalog/pg_database.h" +#include "catalog/storage.h" #include "commands/progress.h" #include "commands/tablespace.h" #include "common/controldata_utils.h" @@ -4564,6 +4565,14 @@ ReadRecord(XLogReaderState *xlogreader, int emode, { ereport(DEBUG1, (errmsg_internal("reached end of WAL in pg_wal, entering archive recovery"))); + + /* cleanup garbage files left during crash recovery */ + ResetUnloggedRelations(UNLOGGED_RELATION_DROP_BUFFER | + UNLOGGED_RELATION_CLEANUP); + + /* run rollback cleanup if any */ + smgrDoPendingDeletes(false); + InArchiveRecovery = true; if (StandbyModeRequested) StandbyMode = true; @@ -7824,6 +7833,14 @@ StartupXLOG(void) } } + /* cleanup garbage files left during crash recovery */ + if (!InArchiveRecovery) + ResetUnloggedRelations(UNLOGGED_RELATION_DROP_BUFFER | + UNLOGGED_RELATION_CLEANUP); + + /* run rollback cleanup if any */ + smgrDoPendingDeletes(false); + /* Allow resource managers to do any required cleanup. */ for (rmid = 0; rmid <= RM_MAX_ID; rmid++) { diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c index c5ad28d71f..d6b30387e9 100644 --- a/src/backend/catalog/storage.c +++ b/src/backend/catalog/storage.c @@ -19,6 +19,7 @@ #include "postgres.h" +#include "access/amapi.h" #include "access/parallel.h" #include "access/visibilitymap.h" #include "access/xact.h" @@ -66,6 +67,23 @@ typedef struct PendingRelDelete struct PendingRelDelete *next; /* linked-list link */ } PendingRelDelete; +#define PCOP_UNLINK_FORK (1 << 0) +#define PCOP_UNLINK_MARK (1 << 1) +#define PCOP_SET_PERSISTENCE (1 << 2) + +typedef struct PendingCleanup +{ + RelFileNode relnode; /* relation that may need to be deleted */ + int op; /* operation mask */ + bool bufpersistence; /* buffer persistence to set */ + int unlink_forknum; /* forknum to unlink */ + StorageMarks unlink_mark; /* mark to unlink */ + BackendId backend; /* InvalidBackendId if not a temp rel */ + bool atCommit; /* T=delete at commit; F=delete at abort */ + int nestLevel; /* xact nesting level of request */ + struct PendingCleanup *next; /* linked-list link */ +} PendingCleanup; + typedef struct PendingRelSync { RelFileNode rnode; @@ -73,6 +91,7 @@ typedef struct PendingRelSync } PendingRelSync; static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */ +static PendingCleanup *pendingCleanups = NULL; /* head of linked list */ HTAB *pendingSyncHash = NULL; @@ -117,7 +136,8 @@ AddPendingSync(const RelFileNode *rnode) SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence) { - PendingRelDelete *pending; + PendingRelDelete *pendingdel; + PendingCleanup *pendingclean; SMgrRelation srel; BackendId backend; bool needs_wal; @@ -143,21 +163,41 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) return NULL; /* placate compiler */ } + /* + * We are going to create a new storage file. If server crashes before the + * current transaction ends the file needs to be cleaned up. The + * SMGR_MARK_UNCOMMITED mark file prompts that work at the next startup. + */ srel = smgropen(rnode, backend); + log_smgrcreatemark(&rnode, MAIN_FORKNUM, SMGR_MARK_UNCOMMITTED); + smgrcreatemark(srel, MAIN_FORKNUM, SMGR_MARK_UNCOMMITTED, false); smgrcreate(srel, MAIN_FORKNUM, false); if (needs_wal) log_smgrcreate(&srel->smgr_rnode.node, MAIN_FORKNUM); /* Add the relation to the list of stuff to delete at abort */ - pending = (PendingRelDelete *) + pendingdel = (PendingRelDelete *) MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); - pending->relnode = rnode; - pending->backend = backend; - pending->atCommit = false; /* delete if abort */ - pending->nestLevel = GetCurrentTransactionNestLevel(); - pending->next = pendingDeletes; - pendingDeletes = pending; + pendingdel->relnode = rnode; + pendingdel->backend = backend; + pendingdel->atCommit = false; /* delete if abort */ + pendingdel->nestLevel = GetCurrentTransactionNestLevel(); + pendingdel->next = pendingDeletes; + pendingDeletes = pendingdel; + + /* drop mark files at commit */ + pendingclean = (PendingCleanup *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingCleanup)); + pendingclean->relnode = rnode; + pendingclean->op = PCOP_UNLINK_MARK; + pendingclean->unlink_forknum = MAIN_FORKNUM; + pendingclean->unlink_mark = SMGR_MARK_UNCOMMITTED; + pendingclean->backend = backend; + pendingclean->atCommit = true; + pendingclean->nestLevel = GetCurrentTransactionNestLevel(); + pendingclean->next = pendingCleanups; + pendingCleanups = pendingclean; if (relpersistence == RELPERSISTENCE_PERMANENT && !XLogIsNeeded()) { @@ -168,6 +208,203 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) return srel; } +/* + * RelationCreateInitFork + * Create physical storage for the init fork of a relation. + * + * Create the init fork for the relation. + * + * This function is transactional. The creation is WAL-logged, and if the + * transaction aborts later on, the init fork will be removed. + */ +void +RelationCreateInitFork(Relation rel) +{ + RelFileNode rnode = rel->rd_node; + PendingCleanup *pending; + PendingCleanup *prev; + PendingCleanup *next; + SMgrRelation srel; + bool create = true; + + /* switch buffer persistence */ + SetRelationBuffersPersistence(RelationGetSmgr(rel), false, false); + + /* + * If we have entries for init-fork operations on this relation, that means + * that we have already registered pending delete entries to drop an + * init-fork preexisting since before the current transaction started. This + * function reverts that change just by removing the entries. + */ + prev = NULL; + for (pending = pendingCleanups; pending != NULL; pending = next) + { + next = pending->next; + + if (RelFileNodeEquals(rnode, pending->relnode) && + pending->unlink_forknum == INIT_FORKNUM) + { + if (prev) + prev->next = next; + else + pendingCleanups = next; + + pfree(pending); + /* prev does not change */ + + create = false; + } + else + prev = pending; + } + + if (!create) + return; + + /* + * We are going to create an init fork. If server crashes before the + * current transaction ends the init fork left alone corrupts data while + * recovery. The mark file works as the sentinel to identify that + * situation. + */ + srel = smgropen(rnode, InvalidBackendId); + log_smgrcreatemark(&rnode, INIT_FORKNUM, SMGR_MARK_UNCOMMITTED); + smgrcreatemark(srel, INIT_FORKNUM, SMGR_MARK_UNCOMMITTED, false); + + /* We don't have existing init fork, create it. */ + smgrcreate(srel, INIT_FORKNUM, false); + + /* + * index-init fork needs further initialization. ambuildempty shoud do + * WAL-log and file sync by itself but otherwise we do that by ourselves. + */ + if (rel->rd_rel->relkind == RELKIND_INDEX) + rel->rd_indam->ambuildempty(rel); + else + { + log_smgrcreate(&rnode, INIT_FORKNUM); + smgrimmedsync(srel, INIT_FORKNUM); + } + + /* drop the init fork, mark file and revert persistence at abort */ + pending = (PendingCleanup *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingCleanup)); + pending->relnode = rnode; + pending->op = PCOP_UNLINK_FORK | PCOP_UNLINK_MARK | PCOP_SET_PERSISTENCE; + pending->unlink_forknum = INIT_FORKNUM; + pending->unlink_mark = SMGR_MARK_UNCOMMITTED; + pending->bufpersistence = true; + pending->backend = InvalidBackendId; + pending->atCommit = false; + pending->nestLevel = GetCurrentTransactionNestLevel(); + pending->next = pendingCleanups; + pendingCleanups = pending; + + /* drop mark file at commit */ + pending = (PendingCleanup *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingCleanup)); + pending->relnode = rnode; + pending->op = PCOP_UNLINK_MARK; + pending->unlink_forknum = INIT_FORKNUM; + pending->unlink_mark = SMGR_MARK_UNCOMMITTED; + pending->backend = InvalidBackendId; + pending->atCommit = true; + pending->nestLevel = GetCurrentTransactionNestLevel(); + pending->next = pendingCleanups; + pendingCleanups = pending; +} + +/* + * RelationDropInitFork + * Delete physical storage for the init fork of a relation. + * + * Register pending-delete of the init fork. The real deletion is performed by + * smgrDoPendingDeletes at commit. + * + * This function is transactional. If the transaction aborts later on, the + * deletion doesn't happen. + */ +void +RelationDropInitFork(Relation rel) +{ + RelFileNode rnode = rel->rd_node; + PendingCleanup *pending; + PendingCleanup *prev; + PendingCleanup *next; + bool inxact_created = false; + + /* switch buffer persistence */ + SetRelationBuffersPersistence(RelationGetSmgr(rel), true, false); + + /* + * If we have entries for init-fork operations of this relation, that means + * that we have created the init fork in the current transaction. We + * remove the init fork and mark file immediately in that case. Otherwise + * just register pending-delete for the existing init fork. + */ + prev = NULL; + for (pending = pendingCleanups; pending != NULL; pending = next) + { + next = pending->next; + + if (RelFileNodeEquals(rnode, pending->relnode) && + pending->unlink_forknum != INIT_FORKNUM) + { + /* unlink list entry */ + if (prev) + prev->next = next; + else + pendingCleanups = next; + + pfree(pending); + /* prev does not change */ + + inxact_created = true; + } + else + prev = pending; + } + + if (inxact_created) + { + SMgrRelation srel = smgropen(rnode, InvalidBackendId); + + /* + * INIT forks never be loaded to shared buffer so no point in dropping + * buffers for such files. + */ + log_smgrunlinkmark(&rnode, INIT_FORKNUM, SMGR_MARK_UNCOMMITTED); + smgrunlinkmark(srel, INIT_FORKNUM, SMGR_MARK_UNCOMMITTED, false); + log_smgrunlink(&rnode, INIT_FORKNUM); + smgrunlink(srel, INIT_FORKNUM, false); + return; + } + + /* register drop of this init fork file at commit */ + pending = (PendingCleanup *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingCleanup)); + pending->relnode = rnode; + pending->op = PCOP_UNLINK_FORK; + pending->unlink_forknum = INIT_FORKNUM; + pending->backend = InvalidBackendId; + pending->atCommit = true; + pending->nestLevel = GetCurrentTransactionNestLevel(); + pending->next = pendingCleanups; + pendingCleanups = pending; + + /* revert buffer-persistence changes at abort */ + pending = (PendingCleanup *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingCleanup)); + pending->relnode = rnode; + pending->op = PCOP_SET_PERSISTENCE; + pending->bufpersistence = false; + pending->backend = InvalidBackendId; + pending->atCommit = false; + pending->nestLevel = GetCurrentTransactionNestLevel(); + pending->next = pendingCleanups; + pendingCleanups = pending; +} + /* * Perform XLogInsert of an XLOG_SMGR_CREATE record to WAL. */ @@ -187,6 +424,88 @@ log_smgrcreate(const RelFileNode *rnode, ForkNumber forkNum) XLogInsert(RM_SMGR_ID, XLOG_SMGR_CREATE | XLR_SPECIAL_REL_UPDATE); } +/* + * Perform XLogInsert of an XLOG_SMGR_UNLINK record to WAL. + */ +void +log_smgrunlink(const RelFileNode *rnode, ForkNumber forkNum) +{ + xl_smgr_unlink xlrec; + + /* + * Make an XLOG entry reporting the file unlink. + */ + xlrec.rnode = *rnode; + xlrec.forkNum = forkNum; + + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, sizeof(xlrec)); + XLogInsert(RM_SMGR_ID, XLOG_SMGR_UNLINK | XLR_SPECIAL_REL_UPDATE); +} + +/* + * Perform XLogInsert of an XLOG_SMGR_CREATEMARK record to WAL. + */ +void +log_smgrcreatemark(const RelFileNode *rnode, ForkNumber forkNum, + StorageMarks mark) +{ + xl_smgr_mark xlrec; + + /* + * Make an XLOG entry reporting the file creation. + */ + xlrec.rnode = *rnode; + xlrec.forkNum = forkNum; + xlrec.mark = mark; + xlrec.action = XLOG_SMGR_MARK_CREATE; + + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, sizeof(xlrec)); + XLogInsert(RM_SMGR_ID, XLOG_SMGR_MARK | XLR_SPECIAL_REL_UPDATE); +} + +/* + * Perform XLogInsert of an XLOG_SMGR_UNLINKMARK record to WAL. + */ +void +log_smgrunlinkmark(const RelFileNode *rnode, ForkNumber forkNum, + StorageMarks mark) +{ + xl_smgr_mark xlrec; + + /* + * Make an XLOG entry reporting the file creation. + */ + xlrec.rnode = *rnode; + xlrec.forkNum = forkNum; + xlrec.mark = mark; + xlrec.action = XLOG_SMGR_MARK_UNLINK; + + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, sizeof(xlrec)); + XLogInsert(RM_SMGR_ID, XLOG_SMGR_MARK | XLR_SPECIAL_REL_UPDATE); +} + +/* + * Perform XLogInsert of an XLOG_SMGR_BUFPERSISTENCE record to WAL. + */ +void +log_smgrbufpersistence(const RelFileNode *rnode, bool persistence) +{ + xl_smgr_bufpersistence xlrec; + + /* + * Make an XLOG entry reporting the change of buffer persistence. + */ + xlrec.rnode = *rnode; + xlrec.persistence = persistence; + + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, sizeof(xlrec)); + XLogInsert(RM_SMGR_ID, XLOG_SMGR_BUFPERSISTENCE | XLR_SPECIAL_REL_UPDATE); +} + /* * RelationDropStorage * Schedule unlinking of physical storage at transaction commit. @@ -255,6 +574,7 @@ RelationPreserveStorage(RelFileNode rnode, bool atCommit) prev->next = next; else pendingDeletes = next; + pfree(pending); /* prev does not change */ } @@ -673,6 +993,88 @@ smgrDoPendingDeletes(bool isCommit) } } +/* + * smgrDoPendingUnmark() -- Clean up work that emits WAL records + * + * The operations handled in the function emits WAL records, which must be + * emitted before the commit record for the current transaction. + */ +void +smgrDoPendingCleanups(bool isCommit) +{ + int nestLevel = GetCurrentTransactionNestLevel(); + PendingCleanup *pending; + PendingCleanup *prev; + PendingCleanup *next; + + prev = NULL; + for (pending = pendingCleanups; pending != NULL; pending = next) + { + next = pending->next; + if (pending->nestLevel < nestLevel) + { + /* outer-level entries should not be processed yet */ + prev = pending; + } + else + { + /* unlink list entry first, so we don't retry on failure */ + if (prev) + prev->next = next; + else + pendingCleanups = next; + + /* do cleanup if called for */ + if (pending->atCommit == isCommit) + { + SMgrRelation srel; + + srel = smgropen(pending->relnode, pending->backend); + + Assert ((pending->op & + ~(PCOP_UNLINK_FORK | PCOP_UNLINK_MARK | + PCOP_SET_PERSISTENCE)) == 0); + + if (pending->op & PCOP_UNLINK_FORK) + { + /* other forks needs to drop buffers */ + Assert(pending->unlink_forknum == INIT_FORKNUM); + + /* Don't emit wal while recovery. */ + if (!InRecovery) + log_smgrunlink(&pending->relnode, + pending->unlink_forknum); + smgrunlink(srel, pending->unlink_forknum, false); + } + + if (pending->op & PCOP_UNLINK_MARK) + { + SMgrRelation srel; + + if (!InRecovery) + log_smgrunlinkmark(&pending->relnode, + pending->unlink_forknum, + pending->unlink_mark); + srel = smgropen(pending->relnode, pending->backend); + smgrunlinkmark(srel, pending->unlink_forknum, + pending->unlink_mark, InRecovery); + smgrclose(srel); + } + + if (pending->op & PCOP_SET_PERSISTENCE) + { + SetRelationBuffersPersistence(srel, pending->bufpersistence, + InRecovery); + } + } + + /* must explicitly free the list entry */ + pfree(pending); + /* prev does not change */ + } + } +} + /* * smgrDoPendingSyncs() -- Take care of relation syncs at end of xact. */ @@ -933,6 +1335,15 @@ smgr_redo(XLogReaderState *record) reln = smgropen(xlrec->rnode, InvalidBackendId); smgrcreate(reln, xlrec->forkNum, true); } + else if (info == XLOG_SMGR_UNLINK) + { + xl_smgr_unlink *xlrec = (xl_smgr_unlink *) XLogRecGetData(record); + SMgrRelation reln; + + reln = smgropen(xlrec->rnode, InvalidBackendId); + smgrunlink(reln, xlrec->forkNum, true); + smgrclose(reln); + } else if (info == XLOG_SMGR_TRUNCATE) { xl_smgr_truncate *xlrec = (xl_smgr_truncate *) XLogRecGetData(record); @@ -1021,6 +1432,124 @@ smgr_redo(XLogReaderState *record) FreeFakeRelcacheEntry(rel); } + else if (info == XLOG_SMGR_MARK) + { + xl_smgr_mark *xlrec = (xl_smgr_mark *) XLogRecGetData(record); + SMgrRelation reln; + PendingCleanup *pending; + bool created = false; + + reln = smgropen(xlrec->rnode, InvalidBackendId); + + switch (xlrec->action) + { + case XLOG_SMGR_MARK_CREATE: + smgrcreatemark(reln, xlrec->forkNum, xlrec->mark, true); + created = true; + break; + case XLOG_SMGR_MARK_UNLINK: + smgrunlinkmark(reln, xlrec->forkNum, xlrec->mark, true); + break; + default: + elog(ERROR, "unknown smgr_mark action \"%c\"", xlrec->mark); + } + + if (created) + { + /* revert mark file operation at abort */ + pending = (PendingCleanup *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingCleanup)); + pending->relnode = xlrec->rnode; + pending->op = PCOP_UNLINK_MARK; + pending->unlink_forknum = xlrec->forkNum; + pending->unlink_mark = xlrec->mark; + pending->backend = InvalidBackendId; + pending->atCommit = false; + pending->nestLevel = GetCurrentTransactionNestLevel(); + pending->next = pendingCleanups; + pendingCleanups = pending; + } + else + { + /* + * Delete pending action for this mark file if any. We should have + * at most one entry for this action. + */ + PendingCleanup *prev = NULL; + + for (pending = pendingCleanups; pending != NULL; + pending = pending->next) + { + if (RelFileNodeEquals(xlrec->rnode, pending->relnode) && + pending->unlink_forknum == xlrec->forkNum && + (pending->op & PCOP_UNLINK_MARK) != 0) + { + if (prev) + prev->next = pending->next; + else + pendingCleanups = pending->next; + + pfree(pending); + break; + } + + prev = pending; + } + } + } + else if (info == XLOG_SMGR_BUFPERSISTENCE) + { + xl_smgr_bufpersistence *xlrec = + (xl_smgr_bufpersistence *) XLogRecGetData(record); + SMgrRelation reln; + PendingCleanup *pending; + PendingCleanup *prev = NULL; + + reln = smgropen(xlrec->rnode, InvalidBackendId); + SetRelationBuffersPersistence(reln, xlrec->persistence, true); + + /* + * Delete pending action for persistence change if any. We should have + * at most one entry for this action. + */ + for (pending = pendingCleanups; pending != NULL; + pending = pending->next) + { + if (RelFileNodeEquals(xlrec->rnode, pending->relnode) && + (pending->op & PCOP_SET_PERSISTENCE) != 0) + { + Assert (pending->bufpersistence == xlrec->persistence); + + if (prev) + prev->next = pending->next; + else + pendingCleanups = pending->next; + + pfree(pending); + break; + } + + prev = pending; + } + + /* + * Revert buffer-persistence changes at abort if the relation is going + * to different persistence from before this transaction. + */ + if (!pending) + { + pending = (PendingCleanup *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingCleanup)); + pending->relnode = xlrec->rnode; + pending->op = PCOP_SET_PERSISTENCE; + pending->bufpersistence = !xlrec->persistence; + pending->backend = InvalidBackendId; + pending->atCommit = false; + pending->nestLevel = GetCurrentTransactionNestLevel(); + pending->next = pendingCleanups; + pendingCleanups = pending; + } + } else elog(PANIC, "smgr_redo: unknown op code %u", info); } diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 3631b8a929..848fda40ca 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -52,6 +52,7 @@ #include "commands/defrem.h" #include "commands/event_trigger.h" #include "commands/policy.h" +#include "commands/progress.h" #include "commands/sequence.h" #include "commands/tablecmds.h" #include "commands/tablespace.h" @@ -5329,6 +5330,187 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, return newcmd; } +/* + * RelationChangePersistence: do in-place persistence change of a relation + */ +static void +RelationChangePersistence(AlteredTableInfo *tab, char persistence, + LOCKMODE lockmode) +{ + Relation rel; + Relation classRel; + HeapTuple tuple, + newtuple; + Datum new_val[Natts_pg_class]; + bool new_null[Natts_pg_class], + new_repl[Natts_pg_class]; + int i; + List *relids; + ListCell *lc_oid; + + Assert(tab->rewrite == AT_REWRITE_ALTER_PERSISTENCE); + Assert(lockmode == AccessExclusiveLock); + + /* + * Under the following condition, we need to call ATRewriteTable, which + * cannot be false in the AT_REWRITE_ALTER_PERSISTENCE case. + */ + Assert(tab->constraints == NULL && tab->partition_constraint == NULL && + tab->newvals == NULL && !tab->verify_new_notnull); + + rel = table_open(tab->relid, lockmode); + + Assert(rel->rd_rel->relpersistence != persistence); + + elog(DEBUG1, "perform in-place persistnce change"); + + /* + * First we collect all relations that we need to change persistence. + */ + + /* Collect OIDs of indexes and toast relations */ + relids = RelationGetIndexList(rel); + relids = lcons_oid(rel->rd_id, relids); + + /* Add toast relation if any */ + if (OidIsValid(rel->rd_rel->reltoastrelid)) + { + List *toastidx; + Relation toastrel = table_open(rel->rd_rel->reltoastrelid, lockmode); + + relids = lappend_oid(relids, rel->rd_rel->reltoastrelid); + toastidx = RelationGetIndexList(toastrel); + relids = list_concat(relids, toastidx); + pfree(toastidx); + table_close(toastrel, NoLock); + } + + table_close(rel, NoLock); + + /* Make changes in storage */ + classRel = table_open(RelationRelationId, RowExclusiveLock); + + foreach (lc_oid, relids) + { + Oid reloid = lfirst_oid(lc_oid); + Relation r = relation_open(reloid, lockmode); + + /* + * XXXX: Some access methods do not bear up an in-place persistence + * change. Specifically, GiST uses page LSNs to figure out whether a + * block has changed, where UNLOGGED GiST indexes use fake LSNs that + * are incompatible with real LSNs used for LOGGED ones. + * + * Maybe if gistGetFakeLSN behaved the same way for permanent and + * unlogged indexes, we could skip index rebuild in exchange of some + * extra WAL records emitted while it is unlogged. + * + * Check relam against a positive list so that we take this way for + * unknown AMs. + */ + if (r->rd_rel->relkind == RELKIND_INDEX && + /* GiST is excluded */ + r->rd_rel->relam != BTREE_AM_OID && + r->rd_rel->relam != HASH_AM_OID && + r->rd_rel->relam != GIN_AM_OID && + r->rd_rel->relam != SPGIST_AM_OID && + r->rd_rel->relam != BRIN_AM_OID) + { + int reindex_flags; + ReindexParams params = {0}; + + /* reindex doesn't allow concurrent use of the index */ + table_close(r, NoLock); + + reindex_flags = + REINDEX_REL_SUPPRESS_INDEX_USE | + REINDEX_REL_CHECK_CONSTRAINTS; + + /* Set the same persistence with the parent relation. */ + if (persistence == RELPERSISTENCE_UNLOGGED) + reindex_flags |= REINDEX_REL_FORCE_INDEXES_UNLOGGED; + else + reindex_flags |= REINDEX_REL_FORCE_INDEXES_PERMANENT; + + reindex_index(reloid, reindex_flags, persistence, ¶ms); + + continue; + } + + /* Create or drop init fork */ + if (persistence == RELPERSISTENCE_UNLOGGED) + RelationCreateInitFork(r); + else + RelationDropInitFork(r); + + /* + * When this relation gets WAL-logged, immediately sync all files but + * initfork to establish the initial state on storage. Buffers have + * already flushed out by RelationCreate(Drop)InitFork called just + * above. Initfork should have been synced as needed. + */ + if (persistence == RELPERSISTENCE_PERMANENT) + { + for (i = 0 ; i < INIT_FORKNUM ; i++) + { + if (smgrexists(RelationGetSmgr(r), i)) + smgrimmedsync(RelationGetSmgr(r), i); + } + } + + /* Update catalog */ + tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", reloid); + + memset(new_val, 0, sizeof(new_val)); + memset(new_null, false, sizeof(new_null)); + memset(new_repl, false, sizeof(new_repl)); + + new_val[Anum_pg_class_relpersistence - 1] = CharGetDatum(persistence); + new_null[Anum_pg_class_relpersistence - 1] = false; + new_repl[Anum_pg_class_relpersistence - 1] = true; + + newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel), + new_val, new_null, new_repl); + + CatalogTupleUpdate(classRel, &newtuple->t_self, newtuple); + heap_freetuple(newtuple); + + /* + * While wal_level >= replica, switching to LOGGED requires the + * relation content to be WAL-logged to recover the table. + * We don't emit this fhile wal_level = minimal. + */ + if (persistence == RELPERSISTENCE_PERMANENT && XLogIsNeeded()) + { + ForkNumber fork; + xl_smgr_truncate xlrec; + + xlrec.blkno = 0; + xlrec.rnode = r->rd_node; + xlrec.flags = SMGR_TRUNCATE_ALL; + + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, sizeof(xlrec)); + + XLogInsert(RM_SMGR_ID, XLOG_SMGR_TRUNCATE | XLR_SPECIAL_REL_UPDATE); + + for (fork = 0; fork < INIT_FORKNUM ; fork++) + { + if (smgrexists(RelationGetSmgr(r), fork)) + log_newpage_range(r, fork, 0, + smgrnblocks(RelationGetSmgr(r), fork), + false); + } + } + + table_close(r, NoLock); + } + + table_close(classRel, NoLock); +} + /* * ATRewriteTables: ALTER TABLE phase 3 */ @@ -5459,47 +5641,55 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, tab->relid, tab->rewrite); - /* - * Create transient table that will receive the modified data. - * - * Ensure it is marked correctly as logged or unlogged. We have - * to do this here so that buffers for the new relfilenode will - * have the right persistence set, and at the same time ensure - * that the original filenode's buffers will get read in with the - * correct setting (i.e. the original one). Otherwise a rollback - * after the rewrite would possibly result with buffers for the - * original filenode having the wrong persistence setting. - * - * NB: This relies on swap_relation_files() also swapping the - * persistence. That wouldn't work for pg_class, but that can't be - * unlogged anyway. - */ - OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, NewAccessMethod, - persistence, lockmode); + if (tab->rewrite == AT_REWRITE_ALTER_PERSISTENCE) + RelationChangePersistence(tab, persistence, lockmode); + else + { + /* + * Create transient table that will receive the modified data. + * + * Ensure it is marked correctly as logged or unlogged. We + * have to do this here so that buffers for the new relfilenode + * will have the right persistence set, and at the same time + * ensure that the original filenode's buffers will get read in + * with the correct setting (i.e. the original one). Otherwise + * a rollback after the rewrite would possibly result with + * buffers for the original filenode having the wrong + * persistence setting. + * + * NB: This relies on swap_relation_files() also swapping the + * persistence. That wouldn't work for pg_class, but that can't + * be unlogged anyway. + */ + OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, + NewAccessMethod, + persistence, lockmode); - /* - * Copy the heap data into the new table with the desired - * modifications, and test the current data within the table - * against new constraints generated by ALTER TABLE commands. - */ - ATRewriteTable(tab, OIDNewHeap, lockmode); + /* + * Copy the heap data into the new table with the desired + * modifications, and test the current data within the table + * against new constraints generated by ALTER TABLE commands. + */ + ATRewriteTable(tab, OIDNewHeap, lockmode); - /* - * Swap the physical files of the old and new heaps, then rebuild - * indexes and discard the old heap. We can use RecentXmin for - * the table's new relfrozenxid because we rewrote all the tuples - * in ATRewriteTable, so no older Xid remains in the table. Also, - * we never try to swap toast tables by content, since we have no - * interest in letting this code work on system catalogs. - */ - finish_heap_swap(tab->relid, OIDNewHeap, - false, false, true, - !OidIsValid(tab->newTableSpace), - RecentXmin, - ReadNextMultiXactId(), - persistence); + /* + * Swap the physical files of the old and new heaps, then + * rebuild indexes and discard the old heap. We can use + * RecentXmin for the table's new relfrozenxid because we + * rewrote all the tuples in ATRewriteTable, so no older Xid + * remains in the table. Also, we never try to swap toast + * tables by content, since we have no interest in letting this + * code work on system catalogs. + */ + finish_heap_swap(tab->relid, OIDNewHeap, + false, false, true, + !OidIsValid(tab->newTableSpace), + RecentXmin, + ReadNextMultiXactId(), + persistence); - InvokeObjectPostAlterHook(RelationRelationId, tab->relid, 0); + InvokeObjectPostAlterHook(RelationRelationId, tab->relid, 0); + } } else { diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c index ec0485705d..45e1a5d817 100644 --- a/src/backend/replication/basebackup.c +++ b/src/backend/replication/basebackup.c @@ -1070,6 +1070,7 @@ sendDir(bbsink *sink, const char *path, int basepathlen, bool sizeonly, bool excludeFound; ForkNumber relForkNum; /* Type of fork if file is a relation */ int relOidChars; /* Chars in filename that are the rel oid */ + StorageMarks mark; /* Skip special stuff */ if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) @@ -1120,7 +1121,7 @@ sendDir(bbsink *sink, const char *path, int basepathlen, bool sizeonly, /* Exclude all forks for unlogged tables except the init fork */ if (isDbDir && parse_filename_for_nontemp_relation(de->d_name, &relOidChars, - &relForkNum)) + &relForkNum, &mark)) { /* Never exclude init forks */ if (relForkNum != INIT_FORKNUM) diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index b4532948d3..dab74bf99a 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -37,6 +37,7 @@ #include "access/xlogutils.h" #include "catalog/catalog.h" #include "catalog/storage.h" +#include "catalog/storage_xlog.h" #include "executor/instrument.h" #include "lib/binaryheap.h" #include "miscadmin.h" @@ -3154,6 +3155,93 @@ DropRelFileNodeBuffers(SMgrRelation smgr_reln, ForkNumber *forkNum, } } +/* --------------------------------------------------------------------- + * SetRelFileNodeBuffersPersistence + * + * This function changes the persistence of all buffer pages of a relation + * then writes all dirty pages of the relation out to disk when switching + * to PERMANENT. (or more accurately, out to kernel disk buffers), + * ensuring that the kernel has an up-to-date view of the relation. + * + * Generally, the caller should be holding AccessExclusiveLock on the + * target relation to ensure that no other backend is busy dirtying + * more blocks of the relation; the effects can't be expected to last + * after the lock is released. + * + * XXX currently it sequentially searches the buffer pool, should be + * changed to more clever ways of searching. This routine is not + * used in any performance-critical code paths, so it's not worth + * adding additional overhead to normal paths to make it go faster; + * but see also DropRelFileNodeBuffers. + * -------------------------------------------------------------------- + */ +void +SetRelationBuffersPersistence(SMgrRelation srel, bool permanent, bool isRedo) +{ + int i; + RelFileNodeBackend rnode = srel->smgr_rnode; + + Assert (!RelFileNodeBackendIsTemp(rnode)); + + if (!isRedo) + log_smgrbufpersistence(&srel->smgr_rnode.node, permanent); + + ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + + for (i = 0; i < NBuffers; i++) + { + BufferDesc *bufHdr = GetBufferDescriptor(i); + uint32 buf_state; + + if (!RelFileNodeEquals(bufHdr->tag.rnode, rnode.node)) + continue; + + ReservePrivateRefCountEntry(); + + buf_state = LockBufHdr(bufHdr); + + if (!RelFileNodeEquals(bufHdr->tag.rnode, rnode.node)) + { + UnlockBufHdr(bufHdr, buf_state); + continue; + } + + if (permanent) + { + /* Init fork is being dropped, drop buffers for it. */ + if (bufHdr->tag.forkNum == INIT_FORKNUM) + { + InvalidateBuffer(bufHdr); + continue; + } + + buf_state |= BM_PERMANENT; + pg_atomic_write_u32(&bufHdr->state, buf_state); + + /* we flush this buffer when switching to PERMANENT */ + if ((buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY)) + { + PinBuffer_Locked(bufHdr); + LWLockAcquire(BufferDescriptorGetContentLock(bufHdr), + LW_SHARED); + FlushBuffer(bufHdr, srel); + LWLockRelease(BufferDescriptorGetContentLock(bufHdr)); + UnpinBuffer(bufHdr, true); + } + else + UnlockBufHdr(bufHdr, buf_state); + } + else + { + /* init fork is always BM_PERMANENT. See BufferAlloc */ + if (bufHdr->tag.forkNum != INIT_FORKNUM) + buf_state &= ~BM_PERMANENT; + + UnlockBufHdr(bufHdr, buf_state); + } + } +} + /* --------------------------------------------------------------------- * DropRelFileNodesAllBuffers * diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index 263057841d..8487ae1f02 100644 --- a/src/backend/storage/file/fd.c +++ b/src/backend/storage/file/fd.c @@ -349,8 +349,6 @@ static void pre_sync_fname(const char *fname, bool isdir, int elevel); static void datadir_fsync_fname(const char *fname, bool isdir, int elevel); static void unlink_if_exists_fname(const char *fname, bool isdir, int elevel); -static int fsync_parent_path(const char *fname, int elevel); - /* * pg_fsync --- do fsync with or without writethrough @@ -3759,7 +3757,7 @@ fsync_fname_ext(const char *fname, bool isdir, bool ignore_perm, int elevel) * This is aimed at making file operations persistent on disk in case of * an OS crash or power failure. */ -static int +int fsync_parent_path(const char *fname, int elevel) { char parentpath[MAXPGPATH]; diff --git a/src/backend/storage/file/reinit.c b/src/backend/storage/file/reinit.c index 0ae3fb6902..0137902bb2 100644 --- a/src/backend/storage/file/reinit.c +++ b/src/backend/storage/file/reinit.c @@ -16,29 +16,49 @@ #include <unistd.h> +#include "access/xlog.h" +#include "catalog/pg_tablespace_d.h" #include "common/relpath.h" #include "postmaster/startup.h" +#include "storage/bufmgr.h" #include "storage/copydir.h" #include "storage/fd.h" +#include "storage/md.h" #include "storage/reinit.h" +#include "storage/smgr.h" #include "utils/hsearch.h" #include "utils/memutils.h" static void ResetUnloggedRelationsInTablespaceDir(const char *tsdirname, - int op); + Oid tspid, int op); static void ResetUnloggedRelationsInDbspaceDir(const char *dbspacedirname, - int op); + Oid tspid, Oid dbid, int op); typedef struct { Oid reloid; /* hash key */ -} unlogged_relation_entry; + bool has_init; /* has INIT fork */ + bool dirty_init; /* needs to remove INIT fork */ + bool dirty_all; /* needs to remove all forks */ +} relfile_entry; /* - * Reset unlogged relations from before the last restart. + * Clean up and reset relation files from before the last restart. * - * If op includes UNLOGGED_RELATION_CLEANUP, we remove all forks of any - * relation with an "init" fork, except for the "init" fork itself. + * If op includes UNLOGGED_RELATION_CLEANUP, we perform different operations + * depending on the existence of the "cleanup" forks. + * + * If SMGR_MARK_UNCOMMITTED mark file for init fork is present, we remove the + * init fork along with the mark file. + * + * If SMGR_MARK_UNCOMMITTED mark file for main fork is present we remove the + * whole relation along with the mark file. + * + * Otherwise, if the "init" fork is found. we remove all forks of any relation + * with the "init" fork, except for the "init" fork itself. + * + * If op includes UNLOGGED_RELATION_DROP_BUFFER, we drop all buffers for all + * relations that have the "cleanup" and/or the "init" forks. * * If op includes UNLOGGED_RELATION_INIT, we copy the "init" fork to the main * fork. @@ -72,7 +92,7 @@ ResetUnloggedRelations(int op) /* * First process unlogged files in pg_default ($PGDATA/base) */ - ResetUnloggedRelationsInTablespaceDir("base", op); + ResetUnloggedRelationsInTablespaceDir("base", DEFAULTTABLESPACE_OID, op); /* * Cycle through directories for all non-default tablespaces. @@ -81,13 +101,19 @@ ResetUnloggedRelations(int op) while ((spc_de = ReadDir(spc_dir, "pg_tblspc")) != NULL) { + Oid tspid; + if (strcmp(spc_de->d_name, ".") == 0 || strcmp(spc_de->d_name, "..") == 0) continue; snprintf(temp_path, sizeof(temp_path), "pg_tblspc/%s/%s", spc_de->d_name, TABLESPACE_VERSION_DIRECTORY); - ResetUnloggedRelationsInTablespaceDir(temp_path, op); + + tspid = atooid(spc_de->d_name); + + Assert(tspid != 0); + ResetUnloggedRelationsInTablespaceDir(temp_path, tspid, op); } FreeDir(spc_dir); @@ -103,7 +129,8 @@ ResetUnloggedRelations(int op) * Process one tablespace directory for ResetUnloggedRelations */ static void -ResetUnloggedRelationsInTablespaceDir(const char *tsdirname, int op) +ResetUnloggedRelationsInTablespaceDir(const char *tsdirname, + Oid tspid, int op) { DIR *ts_dir; struct dirent *de; @@ -130,6 +157,8 @@ ResetUnloggedRelationsInTablespaceDir(const char *tsdirname, int op) while ((de = ReadDir(ts_dir, tsdirname)) != NULL) { + Oid dbid; + /* * We're only interested in the per-database directories, which have * numeric names. Note that this code will also (properly) ignore "." @@ -148,7 +177,10 @@ ResetUnloggedRelationsInTablespaceDir(const char *tsdirname, int op) ereport_startup_progress("resetting unlogged relations (cleanup), elapsed time: %ld.%02d s, current path: %s", dbspace_path); - ResetUnloggedRelationsInDbspaceDir(dbspace_path, op); + dbid = atooid(de->d_name); + Assert(dbid != 0); + + ResetUnloggedRelationsInDbspaceDir(dbspace_path, tspid, dbid, op); } FreeDir(ts_dir); @@ -158,125 +190,227 @@ ResetUnloggedRelationsInTablespaceDir(const char *tsdirname, int op) * Process one per-dbspace directory for ResetUnloggedRelations */ static void -ResetUnloggedRelationsInDbspaceDir(const char *dbspacedirname, int op) +ResetUnloggedRelationsInDbspaceDir(const char *dbspacedirname, + Oid tspid, Oid dbid, int op) { DIR *dbspace_dir; struct dirent *de; char rm_path[MAXPGPATH * 2]; + HTAB *hash; + HASHCTL ctl; /* Caller must specify at least one operation. */ - Assert((op & (UNLOGGED_RELATION_CLEANUP | UNLOGGED_RELATION_INIT)) != 0); + Assert((op & (UNLOGGED_RELATION_CLEANUP | + UNLOGGED_RELATION_DROP_BUFFER | + UNLOGGED_RELATION_INIT)) != 0); /* * Cleanup is a two-pass operation. First, we go through and identify all * the files with init forks. Then, we go through again and nuke * everything with the same OID except the init fork. */ + + /* + * It's possible that someone could create tons of unlogged relations in + * the same database & tablespace, so we'd better use a hash table rather + * than an array or linked list to keep track of which files need to be + * reset. Otherwise, this cleanup operation would be O(n^2). + */ + memset(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(relfile_entry); + hash = hash_create("relfilenode cleanup hash", + 32, &ctl, HASH_ELEM | HASH_BLOBS); + + /* Collect INIT fork and mark files in the directory. */ + dbspace_dir = AllocateDir(dbspacedirname); + while ((de = ReadDir(dbspace_dir, dbspacedirname)) != NULL) + { + int oidchars; + ForkNumber forkNum; + StorageMarks mark; + + /* Skip anything that doesn't look like a relation data file. */ + if (!parse_filename_for_nontemp_relation(de->d_name, &oidchars, + &forkNum, &mark)) + continue; + + if (forkNum == INIT_FORKNUM || mark == SMGR_MARK_UNCOMMITTED) + { + Oid key; + relfile_entry *ent; + bool found; + + /* + * Record the relfilenode information. If it has + * SMGR_MARK_UNCOMMITTED mark files, the relfilenode is in dirty + * state, where clean up is needed. + */ + key = atooid(de->d_name); + ent = hash_search(hash, &key, HASH_ENTER, &found); + + if (!found) + { + ent->has_init = false; + ent->dirty_init = false; + ent->dirty_all = false; + } + + if (forkNum == INIT_FORKNUM && mark == SMGR_MARK_UNCOMMITTED) + ent->dirty_init = true; + else if (forkNum == MAIN_FORKNUM && mark == SMGR_MARK_UNCOMMITTED) + ent->dirty_all = true; + else + { + Assert(forkNum == INIT_FORKNUM); + ent->has_init = true; + } + } + } + + /* Done with the first pass. */ + FreeDir(dbspace_dir); + + /* nothing to do if we don't have init nor cleanup forks */ + if (hash_get_num_entries(hash) < 1) + { + hash_destroy(hash); + return; + } + + if ((op & UNLOGGED_RELATION_DROP_BUFFER) != 0) + { + /* + * When we come here after recovery, smgr object for this file might + * have been created. In that case we need to drop all buffers then the + * smgr object before initializing the unlogged relation. This is safe + * as far as no other backends have accessed the relation before + * starting archive recovery. + */ + HASH_SEQ_STATUS status; + relfile_entry *ent; + SMgrRelation *srels = palloc(sizeof(SMgrRelation) * 8); + int maxrels = 8; + int nrels = 0; + int i; + + Assert(!HotStandbyActive()); + + hash_seq_init(&status, hash); + while((ent = (relfile_entry *) hash_seq_search(&status)) != NULL) + { + RelFileNodeBackend rel; + + /* + * The relation is persistent and stays remain persistent. Don't + * drop the buffers for this relation. + */ + if (ent->has_init && ent->dirty_init) + continue; + + if (maxrels <= nrels) + { + maxrels *= 2; + srels = repalloc(srels, sizeof(SMgrRelation) * maxrels); + } + + rel.backend = InvalidBackendId; + rel.node.spcNode = tspid; + rel.node.dbNode = dbid; + rel.node.relNode = ent->reloid; + + srels[nrels++] = smgropen(rel.node, InvalidBackendId); + } + + DropRelFileNodesAllBuffers(srels, nrels); + + for (i = 0 ; i < nrels ; i++) + smgrclose(srels[i]); + } + + /* + * Now, make a second pass and remove anything that matches. + */ if ((op & UNLOGGED_RELATION_CLEANUP) != 0) { - HTAB *hash; - HASHCTL ctl; - - /* - * It's possible that someone could create a ton of unlogged relations - * in the same database & tablespace, so we'd better use a hash table - * rather than an array or linked list to keep track of which files - * need to be reset. Otherwise, this cleanup operation would be - * O(n^2). - */ - ctl.keysize = sizeof(Oid); - ctl.entrysize = sizeof(unlogged_relation_entry); - ctl.hcxt = CurrentMemoryContext; - hash = hash_create("unlogged relation OIDs", 32, &ctl, - HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); - - /* Scan the directory. */ dbspace_dir = AllocateDir(dbspacedirname); while ((de = ReadDir(dbspace_dir, dbspacedirname)) != NULL) { - ForkNumber forkNum; - int oidchars; - unlogged_relation_entry ent; + ForkNumber forkNum; + StorageMarks mark; + int oidchars; + Oid key; + relfile_entry *ent; + RelFileNodeBackend rel; /* Skip anything that doesn't look like a relation data file. */ if (!parse_filename_for_nontemp_relation(de->d_name, &oidchars, - &forkNum)) - continue; - - /* Also skip it unless this is the init fork. */ - if (forkNum != INIT_FORKNUM) - continue; - - /* - * Put the OID portion of the name into the hash table, if it - * isn't already. - */ - ent.reloid = atooid(de->d_name); - (void) hash_search(hash, &ent, HASH_ENTER, NULL); - } - - /* Done with the first pass. */ - FreeDir(dbspace_dir); - - /* - * If we didn't find any init forks, there's no point in continuing; - * we can bail out now. - */ - if (hash_get_num_entries(hash) == 0) - { - hash_destroy(hash); - return; - } - - /* - * Now, make a second pass and remove anything that matches. - */ - dbspace_dir = AllocateDir(dbspacedirname); - while ((de = ReadDir(dbspace_dir, dbspacedirname)) != NULL) - { - ForkNumber forkNum; - int oidchars; - unlogged_relation_entry ent; - - /* Skip anything that doesn't look like a relation data file. */ - if (!parse_filename_for_nontemp_relation(de->d_name, &oidchars, - &forkNum)) - continue; - - /* We never remove the init fork. */ - if (forkNum == INIT_FORKNUM) + &forkNum, &mark)) continue; /* * See whether the OID portion of the name shows up in the hash * table. If so, nuke it! */ - ent.reloid = atooid(de->d_name); - if (hash_search(hash, &ent, HASH_FIND, NULL)) + key = atooid(de->d_name); + ent = hash_search(hash, &key, HASH_FIND, NULL); + + if (!ent) + continue; + + if (!ent->dirty_all) { - snprintf(rm_path, sizeof(rm_path), "%s/%s", - dbspacedirname, de->d_name); - if (unlink(rm_path) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", - rm_path))); + /* clean permanent relations don't need cleanup */ + if (!ent->has_init) + continue; + + if (ent->dirty_init) + { + /* + * The crashed trasaction did SET UNLOGGED. This relation + * is restored to a LOGGED relation. + */ + if (forkNum != INIT_FORKNUM) + continue; + } else - elog(DEBUG2, "unlinked file \"%s\"", rm_path); + { + /* + * we don't remove the INIT fork of a non-dirty + * relfilenode + */ + if (forkNum == INIT_FORKNUM && mark == SMGR_MARK_NONE) + continue; + } } + + /* so, nuke it! */ + snprintf(rm_path, sizeof(rm_path), "%s/%s", + dbspacedirname, de->d_name); + if (unlink(rm_path) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", + rm_path))); + + rel.backend = InvalidBackendId; + rel.node.spcNode = tspid; + rel.node.dbNode = dbid; + rel.node.relNode = atooid(de->d_name); + + ForgetRelationForkSyncRequests(rel, forkNum); } /* Cleanup is complete. */ FreeDir(dbspace_dir); - hash_destroy(hash); } + hash_destroy(hash); + hash = NULL; + /* * Initialization happens after cleanup is complete: we copy each init - * fork file to the corresponding main fork file. Note that if we are - * asked to do both cleanup and init, we may never get here: if the - * cleanup code determines that there are no init forks in this dbspace, - * it will return before we get to this point. + * fork file to the corresponding main fork file. */ if ((op & UNLOGGED_RELATION_INIT) != 0) { @@ -285,6 +419,7 @@ ResetUnloggedRelationsInDbspaceDir(const char *dbspacedirname, int op) while ((de = ReadDir(dbspace_dir, dbspacedirname)) != NULL) { ForkNumber forkNum; + StorageMarks mark; int oidchars; char oidbuf[OIDCHARS + 1]; char srcpath[MAXPGPATH * 2]; @@ -292,9 +427,11 @@ ResetUnloggedRelationsInDbspaceDir(const char *dbspacedirname, int op) /* Skip anything that doesn't look like a relation data file. */ if (!parse_filename_for_nontemp_relation(de->d_name, &oidchars, - &forkNum)) + &forkNum, &mark)) continue; + Assert(mark == SMGR_MARK_NONE); + /* Also skip it unless this is the init fork. */ if (forkNum != INIT_FORKNUM) continue; @@ -328,15 +465,18 @@ ResetUnloggedRelationsInDbspaceDir(const char *dbspacedirname, int op) while ((de = ReadDir(dbspace_dir, dbspacedirname)) != NULL) { ForkNumber forkNum; + StorageMarks mark; int oidchars; char oidbuf[OIDCHARS + 1]; char mainpath[MAXPGPATH]; /* Skip anything that doesn't look like a relation data file. */ if (!parse_filename_for_nontemp_relation(de->d_name, &oidchars, - &forkNum)) + &forkNum, &mark)) continue; + Assert(mark == SMGR_MARK_NONE); + /* Also skip it unless this is the init fork. */ if (forkNum != INIT_FORKNUM) continue; @@ -379,7 +519,7 @@ ResetUnloggedRelationsInDbspaceDir(const char *dbspacedirname, int op) */ bool parse_filename_for_nontemp_relation(const char *name, int *oidchars, - ForkNumber *fork) + ForkNumber *fork, StorageMarks *mark) { int pos; @@ -410,11 +550,19 @@ parse_filename_for_nontemp_relation(const char *name, int *oidchars, for (segchar = 1; isdigit((unsigned char) name[pos + segchar]); ++segchar) ; - if (segchar <= 1) - return false; - pos += segchar; + if (segchar > 1) + pos += segchar; } + /* mark file? */ + if (name[pos] == '.' && name[pos + 1] != 0) + { + *mark = name[pos + 1]; + pos += 2; + } + else + *mark = SMGR_MARK_NONE; + /* Now we should be at the end. */ if (name[pos] != '\0') return false; diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c index b4bca7eed6..580b74839f 100644 --- a/src/backend/storage/smgr/md.c +++ b/src/backend/storage/smgr/md.c @@ -139,7 +139,8 @@ static MdfdVec *_mdfd_getseg(SMgrRelation reln, ForkNumber forkno, BlockNumber blkno, bool skipFsync, int behavior); static BlockNumber _mdnblocks(SMgrRelation reln, ForkNumber forknum, MdfdVec *seg); - +static bool mdmarkexists(SMgrRelation reln, ForkNumber forkNum, + StorageMarks mark); /* * mdinit() -- Initialize private state for magnetic disk storage manager. @@ -169,6 +170,81 @@ mdexists(SMgrRelation reln, ForkNumber forkNum) return (mdopenfork(reln, forkNum, EXTENSION_RETURN_NULL) != NULL); } +/* + * mdcreatemark() -- Create a mark file. + * + * If isRedo is true, it's okay for the file to exist already. + */ +void +mdcreatemark(SMgrRelation reln, ForkNumber forkNum, StorageMarks mark, + bool isRedo) +{ + char *path =markpath(reln->smgr_rnode, forkNum, mark); + int fd; + + /* See mdcreate for details.. */ + TablespaceCreateDbspace(reln->smgr_rnode.node.spcNode, + reln->smgr_rnode.node.dbNode, + isRedo); + + fd = BasicOpenFile(path, O_WRONLY | O_CREAT | O_EXCL); + if (fd < 0 && (!isRedo || errno != EEXIST)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not crete mark file \"%s\": %m", path))); + + pg_fsync(fd); + close(fd); + + /* + * To guarantee that the creation of the file is persistent, fsync its + * parent directory. + */ + fsync_parent_path(path, ERROR); + + pfree(path); +} + + +/* + * mdunlinkmark() -- Delete the mark file + * + * If isRedo is true, it's okay for the file being not found. + */ +void +mdunlinkmark(SMgrRelation reln, ForkNumber forkNum, StorageMarks mark, + bool isRedo) +{ + char *path = markpath(reln->smgr_rnode, forkNum, mark); + + if (!isRedo || mdmarkexists(reln, forkNum, mark)) + durable_unlink(path, ERROR); + + pfree(path); +} + +/* + * mdmarkexists() -- Check if the file exists. + */ +static bool +mdmarkexists(SMgrRelation reln, ForkNumber forkNum, StorageMarks mark) +{ + char *path = markpath(reln->smgr_rnode, forkNum, mark); + int fd; + + fd = BasicOpenFile(path, O_RDONLY); + if (fd < 0 && errno != ENOENT) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not access mark file \"%s\": %m", path))); + pfree(path); + + if (fd < 0) + return false; + + return true; +} + /* * mdcreate() -- Create a new relation on magnetic disk. * @@ -1025,6 +1101,15 @@ register_forget_request(RelFileNodeBackend rnode, ForkNumber forknum, RegisterSyncRequest(&tag, SYNC_FORGET_REQUEST, true /* retryOnError */ ); } +/* + * ForgetRelationForkSyncRequests -- forget any fsyncs and unlinks for a fork + */ +void +ForgetRelationForkSyncRequests(RelFileNodeBackend rnode, ForkNumber forknum) +{ + register_forget_request(rnode, forknum, 0); +} + /* * ForgetDatabaseSyncRequests -- forget any fsyncs and unlinks for a DB */ @@ -1378,12 +1463,14 @@ mdsyncfiletag(const FileTag *ftag, char *path) * Return 0 on success, -1 on failure, with errno set. */ int -mdunlinkfiletag(const FileTag *ftag, char *path) +mdunlinkfiletag(const FileTag *ftag, char *path, StorageMarks mark) { char *p; /* Compute the path. */ - p = relpathperm(ftag->rnode, MAIN_FORKNUM); + p = GetRelationPath(ftag->rnode.dbNode, ftag->rnode.spcNode, + ftag->rnode.relNode, InvalidBackendId, MAIN_FORKNUM, + mark); strlcpy(path, p, MAXPGPATH); pfree(p); diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c index 0fcef4994b..110e64b0b2 100644 --- a/src/backend/storage/smgr/smgr.c +++ b/src/backend/storage/smgr/smgr.c @@ -62,6 +62,10 @@ typedef struct f_smgr void (*smgr_truncate) (SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks); void (*smgr_immedsync) (SMgrRelation reln, ForkNumber forknum); + void (*smgr_createmark) (SMgrRelation reln, ForkNumber forknum, + StorageMarks mark, bool isRedo); + void (*smgr_unlinkmark) (SMgrRelation reln, ForkNumber forknum, + StorageMarks mark, bool isRedo); } f_smgr; static const f_smgr smgrsw[] = { @@ -82,6 +86,8 @@ static const f_smgr smgrsw[] = { .smgr_nblocks = mdnblocks, .smgr_truncate = mdtruncate, .smgr_immedsync = mdimmedsync, + .smgr_createmark = mdcreatemark, + .smgr_unlinkmark = mdunlinkmark, } }; @@ -335,6 +341,26 @@ smgrcreate(SMgrRelation reln, ForkNumber forknum, bool isRedo) smgrsw[reln->smgr_which].smgr_create(reln, forknum, isRedo); } +/* + * smgrcreatemark() -- Create a mark file + */ +void +smgrcreatemark(SMgrRelation reln, ForkNumber forknum, StorageMarks mark, + bool isRedo) +{ + smgrsw[reln->smgr_which].smgr_createmark(reln, forknum, mark, isRedo); +} + +/* + * smgrunlinkmark() -- Delete a mark file + */ +void +smgrunlinkmark(SMgrRelation reln, ForkNumber forknum, StorageMarks mark, + bool isRedo) +{ + smgrsw[reln->smgr_which].smgr_unlinkmark(reln, forknum, mark, isRedo); +} + /* * smgrdosyncall() -- Immediately sync all forks of all given relations * @@ -662,6 +688,12 @@ smgrimmedsync(SMgrRelation reln, ForkNumber forknum) smgrsw[reln->smgr_which].smgr_immedsync(reln, forknum); } +void +smgrunlink(SMgrRelation reln, ForkNumber forknum, bool isRedo) +{ + smgrsw[reln->smgr_which].smgr_unlink(reln->smgr_rnode, forknum, isRedo); +} + /* * AtEOXact_SMgr * diff --git a/src/backend/storage/sync/sync.c b/src/backend/storage/sync/sync.c index d4083e8a56..9563940d45 100644 --- a/src/backend/storage/sync/sync.c +++ b/src/backend/storage/sync/sync.c @@ -89,7 +89,8 @@ static CycleCtr checkpoint_cycle_ctr = 0; typedef struct SyncOps { int (*sync_syncfiletag) (const FileTag *ftag, char *path); - int (*sync_unlinkfiletag) (const FileTag *ftag, char *path); + int (*sync_unlinkfiletag) (const FileTag *ftag, char *path, + StorageMarks mark); bool (*sync_filetagmatches) (const FileTag *ftag, const FileTag *candidate); } SyncOps; @@ -222,7 +223,8 @@ SyncPostCheckpoint(void) /* Unlink the file */ if (syncsw[entry->tag.handler].sync_unlinkfiletag(&entry->tag, - path) < 0) + path, + SMGR_MARK_NONE) < 0) { /* * There's a race condition, when the database is dropped at the @@ -236,6 +238,20 @@ SyncPostCheckpoint(void) (errcode_for_file_access(), errmsg("could not remove file \"%s\": %m", path))); } + else if (syncsw[entry->tag.handler].sync_unlinkfiletag( + &entry->tag, path, + SMGR_MARK_UNCOMMITTED) < 0) + { + /* + * And we may have SMGR_MARK_UNCOMMITTED file. Remove it if the + * fork files has been successfully removed. It's ok if the file + * does not exist. + */ + if (errno != ENOENT) + ereport(WARNING, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", path))); + } /* Mark the list entry as canceled, just in case */ entry->canceled = true; diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c index 436df54120..dbc0da5da5 100644 --- a/src/bin/pg_rewind/parsexlog.c +++ b/src/bin/pg_rewind/parsexlog.c @@ -407,6 +407,30 @@ extractPageInfo(XLogReaderState *record) * source system. */ } + else if (rmid == RM_SMGR_ID && rminfo == XLOG_SMGR_UNLINK) + { + /* + * We can safely ignore these. When we compare the sizes later on, + * we'll notice that they differ, and copy the missing tail from + * source system. + */ + } + else if (rmid == RM_SMGR_ID && rminfo == XLOG_SMGR_MARK) + { + /* + * We can safely ignore these. When we compare the sizes later on, + * we'll notice that they differ, and copy the missing tail from + * source system. + */ + } + else if (rmid == RM_SMGR_ID && rminfo == XLOG_SMGR_BUFPERSISTENCE) + { + /* + * We can safely ignore these. When we compare the sizes later on, + * we'll notice that they differ, and copy the missing tail from + * source system. + */ + } else if (rmid == RM_XACT_ID && ((rminfo & XLOG_XACT_OPMASK) == XLOG_XACT_COMMIT || (rminfo & XLOG_XACT_OPMASK) == XLOG_XACT_COMMIT_PREPARED || diff --git a/src/common/relpath.c b/src/common/relpath.c index 1f5c426ec0..4945b111cc 100644 --- a/src/common/relpath.c +++ b/src/common/relpath.c @@ -139,9 +139,15 @@ GetDatabasePath(Oid dbNode, Oid spcNode) */ char * GetRelationPath(Oid dbNode, Oid spcNode, Oid relNode, - int backendId, ForkNumber forkNumber) + int backendId, ForkNumber forkNumber, char mark) { char *path; + char markstr[4]; + + if (mark == 0) + markstr[0] = 0; + else + snprintf(markstr, sizeof(markstr), ".%c", mark); if (spcNode == GLOBALTABLESPACE_OID) { @@ -149,10 +155,10 @@ GetRelationPath(Oid dbNode, Oid spcNode, Oid relNode, Assert(dbNode == 0); Assert(backendId == InvalidBackendId); if (forkNumber != MAIN_FORKNUM) - path = psprintf("global/%u_%s", - relNode, forkNames[forkNumber]); + path = psprintf("global/%u_%s%s", + relNode, forkNames[forkNumber], markstr); else - path = psprintf("global/%u", relNode); + path = psprintf("global/%u%s", relNode, markstr); } else if (spcNode == DEFAULTTABLESPACE_OID) { @@ -160,22 +166,22 @@ GetRelationPath(Oid dbNode, Oid spcNode, Oid relNode, if (backendId == InvalidBackendId) { if (forkNumber != MAIN_FORKNUM) - path = psprintf("base/%u/%u_%s", + path = psprintf("base/%u/%u_%s%s", dbNode, relNode, - forkNames[forkNumber]); + forkNames[forkNumber], markstr); else - path = psprintf("base/%u/%u", - dbNode, relNode); + path = psprintf("base/%u/%u%s", + dbNode, relNode, markstr); } else { if (forkNumber != MAIN_FORKNUM) - path = psprintf("base/%u/t%d_%u_%s", + path = psprintf("base/%u/t%d_%u_%s%s", dbNode, backendId, relNode, - forkNames[forkNumber]); + forkNames[forkNumber], markstr); else - path = psprintf("base/%u/t%d_%u", - dbNode, backendId, relNode); + path = psprintf("base/%u/t%d_%u%s", + dbNode, backendId, relNode, markstr); } } else @@ -184,27 +190,28 @@ GetRelationPath(Oid dbNode, Oid spcNode, Oid relNode, if (backendId == InvalidBackendId) { if (forkNumber != MAIN_FORKNUM) - path = psprintf("pg_tblspc/%u/%s/%u/%u_%s", + path = psprintf("pg_tblspc/%u/%s/%u/%u_%s%s", spcNode, TABLESPACE_VERSION_DIRECTORY, dbNode, relNode, - forkNames[forkNumber]); + forkNames[forkNumber], markstr); else - path = psprintf("pg_tblspc/%u/%s/%u/%u", + path = psprintf("pg_tblspc/%u/%s/%u/%u%s", spcNode, TABLESPACE_VERSION_DIRECTORY, - dbNode, relNode); + dbNode, relNode, markstr); } else { if (forkNumber != MAIN_FORKNUM) - path = psprintf("pg_tblspc/%u/%s/%u/t%d_%u_%s", + path = psprintf("pg_tblspc/%u/%s/%u/t%d_%u_%s%s", spcNode, TABLESPACE_VERSION_DIRECTORY, dbNode, backendId, relNode, - forkNames[forkNumber]); + forkNames[forkNumber], markstr); else - path = psprintf("pg_tblspc/%u/%s/%u/t%d_%u", + path = psprintf("pg_tblspc/%u/%s/%u/t%d_%u%s", spcNode, TABLESPACE_VERSION_DIRECTORY, - dbNode, backendId, relNode); + dbNode, backendId, relNode, markstr); } } + return path; } diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h index 0ab32b44e9..584ebac391 100644 --- a/src/include/catalog/storage.h +++ b/src/include/catalog/storage.h @@ -23,6 +23,8 @@ extern int wal_skip_threshold; extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence); +extern void RelationCreateInitFork(Relation rel); +extern void RelationDropInitFork(Relation rel); extern void RelationDropStorage(Relation rel); extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit); extern void RelationPreTruncate(Relation rel); @@ -41,6 +43,7 @@ extern void RestorePendingSyncs(char *startAddress); extern void smgrDoPendingDeletes(bool isCommit); extern void smgrDoPendingSyncs(bool isCommit, bool isParallelWorker); extern int smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr); +extern void smgrDoPendingCleanups(bool isCommit); extern void AtSubCommit_smgr(void); extern void AtSubAbort_smgr(void); extern void PostPrepare_smgr(void); diff --git a/src/include/catalog/storage_xlog.h b/src/include/catalog/storage_xlog.h index f0814f1458..12346ed7f6 100644 --- a/src/include/catalog/storage_xlog.h +++ b/src/include/catalog/storage_xlog.h @@ -18,17 +18,23 @@ #include "lib/stringinfo.h" #include "storage/block.h" #include "storage/relfilenode.h" +#include "storage/smgr.h" /* * Declarations for smgr-related XLOG records * - * Note: we log file creation and truncation here, but logging of deletion - * actions is handled by xact.c, because it is part of transaction commit. + * Note: we log file creation, truncation and buffer persistence change here, + * but logging of deletion actions is handled mainly by xact.c, because it is + * part of transaction commit in most cases. However, there's a case where + * init forks are deleted outside control of transaction. */ /* XLOG gives us high 4 bits */ #define XLOG_SMGR_CREATE 0x10 #define XLOG_SMGR_TRUNCATE 0x20 +#define XLOG_SMGR_UNLINK 0x30 +#define XLOG_SMGR_MARK 0x40 +#define XLOG_SMGR_BUFPERSISTENCE 0x50 typedef struct xl_smgr_create { @@ -36,6 +42,32 @@ typedef struct xl_smgr_create ForkNumber forkNum; } xl_smgr_create; +typedef struct xl_smgr_unlink +{ + RelFileNode rnode; + ForkNumber forkNum; +} xl_smgr_unlink; + +typedef enum smgr_mark_action +{ + XLOG_SMGR_MARK_CREATE = 'c', + XLOG_SMGR_MARK_UNLINK = 'u' +} smgr_mark_action; + +typedef struct xl_smgr_mark +{ + RelFileNode rnode; + ForkNumber forkNum; + StorageMarks mark; + smgr_mark_action action; +} xl_smgr_mark; + +typedef struct xl_smgr_bufpersistence +{ + RelFileNode rnode; + bool persistence; +} xl_smgr_bufpersistence; + /* flags for xl_smgr_truncate */ #define SMGR_TRUNCATE_HEAP 0x0001 #define SMGR_TRUNCATE_VM 0x0002 @@ -51,6 +83,12 @@ typedef struct xl_smgr_truncate } xl_smgr_truncate; extern void log_smgrcreate(const RelFileNode *rnode, ForkNumber forkNum); +extern void log_smgrunlink(const RelFileNode *rnode, ForkNumber forkNum); +extern void log_smgrcreatemark(const RelFileNode *rnode, ForkNumber forkNum, + StorageMarks mark); +extern void log_smgrunlinkmark(const RelFileNode *rnode, ForkNumber forkNum, + StorageMarks mark); +extern void log_smgrbufpersistence(const RelFileNode *rnode, bool persistence); extern void smgr_redo(XLogReaderState *record); extern void smgr_desc(StringInfo buf, XLogReaderState *record); diff --git a/src/include/common/relpath.h b/src/include/common/relpath.h index a44be11ca0..106a5cf508 100644 --- a/src/include/common/relpath.h +++ b/src/include/common/relpath.h @@ -67,7 +67,7 @@ extern int forkname_chars(const char *str, ForkNumber *fork); extern char *GetDatabasePath(Oid dbNode, Oid spcNode); extern char *GetRelationPath(Oid dbNode, Oid spcNode, Oid relNode, - int backendId, ForkNumber forkNumber); + int backendId, ForkNumber forkNumber, char mark); /* * Wrapper macros for GetRelationPath. Beware of multiple @@ -77,7 +77,7 @@ extern char *GetRelationPath(Oid dbNode, Oid spcNode, Oid relNode, /* First argument is a RelFileNode */ #define relpathbackend(rnode, backend, forknum) \ GetRelationPath((rnode).dbNode, (rnode).spcNode, (rnode).relNode, \ - backend, forknum) + backend, forknum, 0) /* First argument is a RelFileNode */ #define relpathperm(rnode, forknum) \ @@ -87,4 +87,9 @@ extern char *GetRelationPath(Oid dbNode, Oid spcNode, Oid relNode, #define relpath(rnode, forknum) \ relpathbackend((rnode).node, (rnode).backend, forknum) +/* First argument is a RelFileNodeBackend */ +#define markpath(rnode, forknum, mark) \ + GetRelationPath((rnode).node.dbNode, (rnode).node.spcNode, \ + (rnode).node.relNode, \ + (rnode).backend, forknum, mark) #endif /* RELPATH_H */ diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h index cfce23ecbc..f5a7df87a4 100644 --- a/src/include/storage/bufmgr.h +++ b/src/include/storage/bufmgr.h @@ -206,6 +206,8 @@ extern void FlushRelationsAllBuffers(struct SMgrRelationData **smgrs, int nrels) extern void FlushDatabaseBuffers(Oid dbid); extern void DropRelFileNodeBuffers(struct SMgrRelationData *smgr_reln, ForkNumber *forkNum, int nforks, BlockNumber *firstDelBlock); +extern void SetRelationBuffersPersistence(struct SMgrRelationData *srel, + bool permanent, bool isRedo); extern void DropRelFileNodesAllBuffers(struct SMgrRelationData **smgr_reln, int nnodes); extern void DropDatabaseBuffers(Oid dbid); diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h index 34602ae006..2dc0357ad5 100644 --- a/src/include/storage/fd.h +++ b/src/include/storage/fd.h @@ -185,6 +185,7 @@ extern ssize_t pg_pwritev_with_retry(int fd, extern int pg_truncate(const char *path, off_t length); extern void fsync_fname(const char *fname, bool isdir); extern int fsync_fname_ext(const char *fname, bool isdir, bool ignore_perm, int elevel); +extern int fsync_parent_path(const char *fname, int elevel); extern int durable_rename(const char *oldfile, const char *newfile, int loglevel); extern int durable_unlink(const char *fname, int loglevel); extern int durable_rename_excl(const char *oldfile, const char *newfile, int loglevel); diff --git a/src/include/storage/md.h b/src/include/storage/md.h index 752b440864..99620816b5 100644 --- a/src/include/storage/md.h +++ b/src/include/storage/md.h @@ -23,6 +23,10 @@ extern void mdinit(void); extern void mdopen(SMgrRelation reln); extern void mdclose(SMgrRelation reln, ForkNumber forknum); +extern void mdcreatemark(SMgrRelation reln, ForkNumber forknum, + StorageMarks mark, bool isRedo); +extern void mdunlinkmark(SMgrRelation reln, ForkNumber forknum, + StorageMarks mark, bool isRedo); extern void mdcreate(SMgrRelation reln, ForkNumber forknum, bool isRedo); extern bool mdexists(SMgrRelation reln, ForkNumber forknum); extern void mdunlink(RelFileNodeBackend rnode, ForkNumber forknum, bool isRedo); @@ -41,12 +45,14 @@ extern void mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks); extern void mdimmedsync(SMgrRelation reln, ForkNumber forknum); +extern void ForgetRelationForkSyncRequests(RelFileNodeBackend rnode, + ForkNumber forknum); extern void ForgetDatabaseSyncRequests(Oid dbid); extern void DropRelationFiles(RelFileNode *delrels, int ndelrels, bool isRedo); /* md sync callbacks */ extern int mdsyncfiletag(const FileTag *ftag, char *path); -extern int mdunlinkfiletag(const FileTag *ftag, char *path); +extern int mdunlinkfiletag(const FileTag *ftag, char *path, StorageMarks mark); extern bool mdfiletagmatches(const FileTag *ftag, const FileTag *candidate); #endif /* MD_H */ diff --git a/src/include/storage/reinit.h b/src/include/storage/reinit.h index fad1e5c473..e1f97e9b89 100644 --- a/src/include/storage/reinit.h +++ b/src/include/storage/reinit.h @@ -16,13 +16,15 @@ #define REINIT_H #include "common/relpath.h" - +#include "storage/smgr.h" extern void ResetUnloggedRelations(int op); -extern bool parse_filename_for_nontemp_relation(const char *name, - int *oidchars, ForkNumber *fork); +extern bool parse_filename_for_nontemp_relation(const char *name, int *oidchars, + ForkNumber *fork, + StorageMarks *mark); #define UNLOGGED_RELATION_CLEANUP 0x0001 -#define UNLOGGED_RELATION_INIT 0x0002 +#define UNLOGGED_RELATION_DROP_BUFFER 0x0002 +#define UNLOGGED_RELATION_INIT 0x0004 #endif /* REINIT_H */ diff --git a/src/include/storage/smgr.h b/src/include/storage/smgr.h index a6fbf7b6a6..201ecace8a 100644 --- a/src/include/storage/smgr.h +++ b/src/include/storage/smgr.h @@ -18,6 +18,18 @@ #include "storage/block.h" #include "storage/relfilenode.h" +/* + * Storage marks is a file of which existence suggests something about a + * file. The name of such files is "<filename>.<mark>", where the mark is one + * of the values of StorageMarks. Since ".<digit>" means segment files so don't + * use digits for the mark character. + */ +typedef enum StorageMarks +{ + SMGR_MARK_NONE = 0, + SMGR_MARK_UNCOMMITTED = 'u' /* the file is not committed yet */ +} StorageMarks; + /* * smgr.c maintains a table of SMgrRelation objects, which are essentially * cached file handles. An SMgrRelation is created (if not already present) @@ -85,7 +97,12 @@ extern void smgrclearowner(SMgrRelation *owner, SMgrRelation reln); extern void smgrclose(SMgrRelation reln); extern void smgrcloseall(void); extern void smgrclosenode(RelFileNodeBackend rnode); +extern void smgrcreatemark(SMgrRelation reln, ForkNumber forknum, + StorageMarks mark, bool isRedo); +extern void smgrunlinkmark(SMgrRelation reln, ForkNumber forknum, + StorageMarks mark, bool isRedo); extern void smgrcreate(SMgrRelation reln, ForkNumber forknum, bool isRedo); +extern void smgrunlink(SMgrRelation reln, ForkNumber forknum, bool isRedo); extern void smgrdosyncall(SMgrRelation *rels, int nrels); extern void smgrdounlinkall(SMgrRelation *rels, int nrels, bool isRedo); extern void smgrextend(SMgrRelation reln, ForkNumber forknum, diff --git a/src/test/recovery/t/027_persistence_change.pl b/src/test/recovery/t/027_persistence_change.pl new file mode 100644 index 0000000000..c2f7076ea9 --- /dev/null +++ b/src/test/recovery/t/027_persistence_change.pl @@ -0,0 +1,247 @@ + +# Copyright (c) 2021, PostgreSQL Global Development Group + +# Test relation persistence change +use strict; +use warnings; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; +use Test::More tests => 30; +use IPC::Run qw(pump finish timer); +use Config; + +my $data_unit = 2000; + +# Initialize primary node. +my $node = PostgreSQL::Test::Cluster->new('node'); +$node->init; +# we don't want checkpointing +$node->append_conf('postgresql.conf', qq( +checkpoint_timeout = '24h' +)); +$node->start; +create($node); + +my $relfilenodes1 = relfilenodes(); + +# correctly recover empty tables +$node->stop('immediate'); +$node->start; +insert($node, 0, $data_unit); + +# data persists after a crash +$node->stop('immediate'); +$node->start; +checkdataloss($data_unit, 'crash logged 1'); + +set_unlogged($node); +# SET UNLOGGED didn't change relfilenode +my $relfilenodes2 = relfilenodes(); +checkrelfilenodes($relfilenodes1, $relfilenodes2, 'logged->unlogged'); + +# data cleanly vanishes after a crash +$node->stop('immediate'); +$node->start; +checkdataloss(0, 'crash unlogged'); + +insert($node, 0, $data_unit); +set_logged($node); + +$node->stop('immediate'); +$node->start; +# SET LOGGED didn't change relfilenode and data survive a crash +my $relfilenodes3 = relfilenodes(); +checkrelfilenodes($relfilenodes2, $relfilenodes3, 'unlogged->logged'); +checkdataloss($data_unit, 'crash logged 2'); + +# unlogged insert -> graceful stop +set_unlogged($node); +insert($node, $data_unit, $data_unit, 0); +$node->stop; +$node->start; +checkdataloss($data_unit * 2, 'unlogged graceful restart'); + +# crash during transaction +set_logged($node); +$node->stop('immediate'); +$node->start; +insert($node, $data_unit * 2, $data_unit); + +my $h = insert($node, $data_unit * 3, $data_unit, 1); ## this is aborted +$node->stop('immediate'); + +# finishing $h stalls this case, just tear it off. +$h = undef; + +# check if indexes are working +$node->start; +# drop first half of data to reduce run time +$node->safe_psql('postgres', 'DELETE FROM t WHERE bt < ' . $data_unit * 2); +check($node, $data_unit * 2, $data_unit * 3 - 1, 'final check'); + +sub create +{ + my ($node) = @_; + + $node->psql('postgres', qq( + CREATE TABLE t (bt int, gin int[], gist point, hash int, + brin int, spgist point); + CREATE INDEX i_bt ON t USING btree (bt); + CREATE INDEX i_gin ON t USING gin (gin); + CREATE INDEX i_gist ON t USING gist (gist); + CREATE INDEX i_hash ON t USING hash (hash); + CREATE INDEX i_brin ON t USING brin (brin); + CREATE INDEX i_spgist ON t USING spgist (spgist);)); +} + + +sub insert +{ + my ($node, $st, $num, $interactive) = @_; + my $ed = $st + $num - 1; + my $query = qq(BEGIN; +INSERT INTO t + (SELECT i, ARRAY[i, i * 2], point(i, i * 2), i, i, point(i, i) + FROM generate_series($st, $ed) i); +); + + if ($interactive) + { + my $in = ''; + my $out = ''; + my $timer = timer(10); + + my $h = $node->interactive_psql('postgres', \$in, \$out, $timer); + like($out, qr/psql/, "print startup banner"); + + $timer->start(10); + $in .= $query . "SELECT 'END';\n"; + pump $h until ($out =~ /\nEND/ || $timer->is_expired); + ok(($out =~ /\nEND/ && !$timer->is_expired), "inserted-$st-$num"); + return $h + # the trasaction is not terminated + } + else + { + $node->psql('postgres', $query . "COMMIT;"); + return undef; + } +} + +sub check +{ + my ($node, $st, $ed, $head) = @_; + my $num_data = $ed - $st + 1; + + is($node->safe_psql('postgres', qq( + SET enable_seqscan TO true; + SET enable_indexscan TO false; + SELECT COUNT(*) FROM t, generate_series($st, $ed) i + WHERE bt = i)), + $num_data, "$head: heap is not broken"); + is($node->safe_psql('postgres', qq( + SET enable_seqscan TO false; + SET enable_indexscan TO true; + SELECT COUNT(*) FROM t, generate_series($st, $ed) i + WHERE bt = i)), + $num_data, "$head: btree is not broken"); + is($node->safe_psql('postgres', qq( + SET enable_seqscan TO false; + SET enable_indexscan TO true; + SELECT COUNT(*) FROM t, generate_series($st, $ed) i + WHERE gin = ARRAY[i, i * 2];)), + $num_data, "$head: gin is not broken"); + is($node->safe_psql('postgres', qq( + SET enable_seqscan TO false; + SET enable_indexscan TO true; + SELECT COUNT(*) FROM t, generate_series($st, $ed) i + WHERE gist <@ box(point(i-0.5, i*2-0.5),point(i+0.5, i*2+0.5));)), + $num_data, "$head: gist is not broken"); + is($node->safe_psql('postgres', qq( + SET enable_seqscan TO false; + SET enable_indexscan TO true; + SELECT COUNT(*) FROM t, generate_series($st, $ed) i + WHERE hash = i;)), + $num_data, "$head: hash is not broken"); + is($node->safe_psql('postgres', qq( + SET enable_seqscan TO false; + SET enable_indexscan TO true; + SELECT COUNT(*) FROM t, generate_series($st, $ed) i + WHERE brin = i;)), + $num_data, "$head: brin is not broken"); + is($node->safe_psql('postgres', qq( + SET enable_seqscan TO false; + SET enable_indexscan TO true; + SELECT COUNT(*) FROM t, generate_series($st, $ed) i + WHERE spgist <@ box(point(i-0.5,i-0.5),point(i+0.5,i+0.5));)), + $num_data, "$head: spgist is not broken"); +} + +sub set_unlogged +{ + my ($node) = @_; + + $node->psql('postgres', qq( + ALTER TABLE t SET UNLOGGED; +)); +} + +sub set_logged +{ + my ($node) = @_; + + $node->psql('postgres', qq( + ALTER TABLE t SET LOGGED; +)); +} + +sub relfilenodes +{ + my $result = $node->safe_psql('postgres', qq{ + SELECT relname, relfilenode FROM pg_class + WHERE relname + IN ('t', 'i_bt','i_gin','i_gist','i_hash','i_brin','i_spgist');}); + + my %relfilenodes; + + foreach my $l (split(/\n/, $result)) + { + die "unexpected format: $l" if ($l !~ /^([^|]+)\|([0-9]+)$/); + $relfilenodes{$1} = $2; + } + + # the number must correspond to the in list above + is (scalar %relfilenodes, 7, "number of relations is correct"); + + return \%relfilenodes; +} + +sub checkrelfilenodes +{ + my ($rnodes1, $rnodes2, $s) = @_; + + foreach my $n (keys %{$rnodes1}) + { + if ($n eq 'i_gist') + { + # persistence of GiST index is not changed in-place + isnt($rnodes1->{$n}, $rnodes2->{$n}, + "$s: relfilenode is changed: $n"); + } + else + { + # otherwise all relations are processed in-place + is($rnodes1->{$n}, $rnodes2->{$n}, + "$s: relfilenode is not changed: $n"); + } + } +} + +sub checkdataloss +{ + my ($expected, $s) = @_; + + is($node->safe_psql('postgres', "SELECT count(*) FROM t;"), $expected, + "$s: data in table t is in the expected state"); +} -- 2.27.0 ----Next_Part(Thu_Jan__6_13_30_17_2022_597)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v14-0002-New-command-ALTER-TABLE-ALL-IN-TABLESPACE-SET-LO.patch" ^ permalink raw reply [nested|flat] 3+ messages in thread
* Re: [PATCH] New [relation] option engine @ 2025-02-10 18:35 Nikolay Shaplov <[email protected]> 2025-02-26 19:33 ` Re: [PATCH] New [relation] option engine Nikolay Shaplov <[email protected]> 0 siblings, 1 reply; 3+ messages in thread From: Nikolay Shaplov @ 2025-02-10 18:35 UTC (permalink / raw) To: jian he <[email protected]>; [email protected]; +Cc: Peter Smith <[email protected]>; Alvaro Herrera <[email protected]>; Yura Sokolov <[email protected]>; [email protected]; Michael Paquier <[email protected]>; Nikolay Shaplov <[email protected]> I've rebased patch against current master, added new autovacuum option added in 306dc520 and make changes according to "Turn 'validnsps' static variables into locals" patch 1e35951e Now I gess I will try to split the patch into several parts (two for a start). I do not think it should be commit part by part, it will make no sense, but this should simplify reading and understanding. -- Nikolay Shaplov aka Nataraj Fuzzing Engineer at Postgres Professional Matrix IM: @dhyan:nataraj.su Attachments: [text/x-patch] v10-0001-New-options-engine.patch (206.6K, ../../2220883.Mh6RI2rZIc@thinkpad-pgpro/2-v10-0001-New-options-engine.patch) download | inline diff: From e6cafc6e9b4aa8c7adb902a70a0386394d4854a3 Mon Sep 17 00:00:00 2001 From: Nikolay Shaplov <[email protected]> Date: Mon, 10 Feb 2025 21:08:17 +0300 Subject: [PATCH v10 1/1] New options engine Add new options engine that will allow to define options directly in any part of code they are needed (anywhere, not only for relation and op. classes) and use unified API for option processing. This will allow: - Unify options code. Now we have core AM reloptions, contrib AM reloptions and local reloptions for op. classes, each using their own API. New engine will allow to use single API for them all. - Move options definition completely to Access Method implementation. Done for index AM, will be done storage AM later. (Before core AM all options were defined in reloptions.c) - All options for AM is defined in one single chunk of code. (Before for core AM you had to edit both reloptions.c and code of AM, this was error prone) - More simple way to define new option stets (we will need partitioned.* options sooner or later, this engine will allow to do it more easy) - Allow to add name=value options anywhere they needed. - More flexible way for defining option behavior (e.g. disable OIDS=true via postvalidate callback, without changing option processing code) What is done, and what is still in progress: - Index AM options now using new option engine the way it should be. - Options for Heap and Toast, are also using new engine, but they are still located in reloptions.c file as they were before. They will be moved to storage AM later. - local_options API that is used for op. class options is now wrapper around new option engine. Later on, build in op. classes will be updated to use new option engine directly, and local_options API can be kept for backward compatibility for a while. This patch does not change postgres behavior. All existing options work the way they did it before. One single error message is changed, and it is more accurate now. ``` -ERROR: unrecognized parameter "not_existing_option" +ERROR: unrecognized parameter "toast.not_existing_option" ``` General concepts of the new engine: - `option_spec` is a C-structure that completely describes single option, how it should be parsed, validated and stored: name, type, acceptable values, offset in bytea representation, postvalidate call back, etc. We have `option_spec_*` for each option type. - `options_spec_set` is a set of `option_spec`s that are available for certain context (e.g. for certain AM reloptions). It is a list of option_spec`s plus a callback for overall validation (e.g. make sure optionA < optionB) - There are four representations of options. The way they came from SQL parser (defList), the way they are stored in DB (TEXT[]), the binary representation that is used by outer code (bytea), and internal representation that is used by option engine while parsing and validating options (Option Value List) - There are functions to convert options from one representation to another (possibly with validation if applicable): `optionsTextArrayToDefList`, `optionsDefListToTextArray`, `optionsTextArrayToBytea` and others. - There are functions for defining `option_spec_set`: `allocateOptionsSpecSet`, `optionsSpecSetAddBool`, `optionsSpecSetAddInt`, etc. Outer code, that would like to use option engine, should define it's own `option_spec_set`, implement getting options from SQL parser, getting and putting them to DB storage, passing binary representation of the option to the code that would actually use them. For parsing, validating and converting options from one representation to another, outer code should use functions from options engine function set. Each function will use information from `option_spec_set` to determinate what options are available and how exactly they should be parsed and validated. This is only a start of option code reworking. Further improvements listed above and other suggestion will follow. --- contrib/bloom/bloom.h | 4 +- contrib/bloom/blutils.c | 102 +- contrib/dblink/dblink.c | 2 +- contrib/file_fdw/file_fdw.c | 2 +- contrib/postgres_fdw/option.c | 2 +- contrib/test_decoding/expected/twophase.out | 3 +- doc/src/sgml/indexam.sgml | 25 +- src/backend/access/brin/brin.c | 46 +- src/backend/access/common/Makefile | 1 + src/backend/access/common/meson.build | 1 + src/backend/access/common/options.c | 1367 ++++++++++ src/backend/access/common/reloptions.c | 2259 ++++------------- src/backend/access/gin/ginutil.c | 46 +- src/backend/access/gist/gist.c | 2 +- src/backend/access/gist/gistutil.c | 52 +- src/backend/access/hash/hash.c | 2 +- src/backend/access/hash/hashutil.c | 34 +- src/backend/access/nbtree/nbtree.c | 35 +- src/backend/access/nbtree/nbtutils.c | 20 +- src/backend/access/spgist/spgutils.c | 41 +- src/backend/commands/createas.c | 11 +- src/backend/commands/foreigncmds.c | 2 +- src/backend/commands/indexcmds.c | 22 +- src/backend/commands/tablecmds.c | 117 +- src/backend/commands/tablespace.c | 16 +- src/backend/foreign/foreign.c | 14 +- src/backend/parser/parse_utilcmd.c | 4 +- src/backend/tcop/utility.c | 29 +- src/backend/utils/cache/attoptcache.c | 4 +- src/backend/utils/cache/relcache.c | 8 +- src/backend/utils/cache/spccache.c | 3 +- src/include/access/amapi.h | 9 +- src/include/access/brin.h | 2 + src/include/access/brin_internal.h | 1 + src/include/access/gin_private.h | 1 + src/include/access/gist_private.h | 4 +- src/include/access/hash.h | 2 +- src/include/access/nbtree.h | 2 +- src/include/access/options.h | 274 ++ src/include/access/reloptions.h | 204 +- src/include/access/spgist.h | 3 - src/include/access/spgist_private.h | 1 + src/include/commands/tablecmds.h | 2 +- .../modules/dummy_index_am/dummy_index_am.c | 148 +- .../test_oat_hooks/expected/alter_table.out | 16 + src/test/regress/expected/reloptions.out | 13 +- src/test/regress/sql/reloptions.sql | 7 + 47 files changed, 2623 insertions(+), 2342 deletions(-) create mode 100644 src/backend/access/common/options.c create mode 100644 src/include/access/options.h diff --git a/contrib/bloom/bloom.h b/contrib/bloom/bloom.h index 648167045f4..47f697f66a8 100644 --- a/contrib/bloom/bloom.h +++ b/contrib/bloom/bloom.h @@ -17,6 +17,7 @@ #include "access/generic_xlog.h" #include "access/itup.h" #include "access/xlog.h" +#include "access/options.h" #include "fmgr.h" #include "nodes/pathnodes.h" @@ -203,7 +204,8 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *bloptions(Datum reloptions, bool validate); +extern options_spec_set *blrelopt_specset(void); +extern void blReloptionPostprocess(void *, bool validate); extern void blcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, Cost *indexStartupCost, Cost *indexTotalCost, Selectivity *indexSelectivity, diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c index 04b61042a57..b4f55daf49b 100644 --- a/contrib/bloom/blutils.c +++ b/contrib/bloom/blutils.c @@ -15,7 +15,7 @@ #include "access/amapi.h" #include "access/generic_xlog.h" -#include "access/reloptions.h" +#include "access/options.h" #include "bloom.h" #include "commands/vacuum.h" #include "storage/bufmgr.h" @@ -31,52 +31,12 @@ PG_FUNCTION_INFO_V1(blhandler); -/* Kind of relation options for bloom index */ -static relopt_kind bl_relopt_kind; - -/* parse table for fillRelOptions */ -static relopt_parse_elt bl_relopt_tab[INDEX_MAX_KEYS + 1]; +/* Catalog of relation options for bloom index */ +static options_spec_set *bl_relopt_specset; static int32 myRand(void); static void mySrand(uint32 seed); -/* - * Module initialize function: initialize info about Bloom relation options. - * - * Note: keep this in sync with makeDefaultBloomOptions(). - */ -void -_PG_init(void) -{ - int i; - char buf[16]; - - bl_relopt_kind = add_reloption_kind(); - - /* Option for length of signature */ - add_int_reloption(bl_relopt_kind, "length", - "Length of signature in bits", - DEFAULT_BLOOM_LENGTH, 1, MAX_BLOOM_LENGTH, - AccessExclusiveLock); - bl_relopt_tab[0].optname = "length"; - bl_relopt_tab[0].opttype = RELOPT_TYPE_INT; - bl_relopt_tab[0].offset = offsetof(BloomOptions, bloomLength); - - /* Number of bits for each possible index column: col1, col2, ... */ - for (i = 0; i < INDEX_MAX_KEYS; i++) - { - snprintf(buf, sizeof(buf), "col%d", i + 1); - add_int_reloption(bl_relopt_kind, buf, - "Number of bits generated for each index column", - DEFAULT_BLOOM_BITS, 1, MAX_BLOOM_BITS, - AccessExclusiveLock); - bl_relopt_tab[i + 1].optname = MemoryContextStrdup(TopMemoryContext, - buf); - bl_relopt_tab[i + 1].opttype = RELOPT_TYPE_INT; - bl_relopt_tab[i + 1].offset = offsetof(BloomOptions, bitSize[0]) + sizeof(int) * i; - } -} - /* * Construct a default set of Bloom options. */ @@ -135,7 +95,7 @@ blhandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; amroutine->amgettreeheight = NULL; - amroutine->amoptions = bloptions; + amroutine->amreloptspecset = blrelopt_specset; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = blvalidate; @@ -156,6 +116,15 @@ blhandler(PG_FUNCTION_ARGS) PG_RETURN_POINTER(amroutine); } +void +blReloptionPostprocess(void *data, bool validate) +{ + BloomOptions *opts = (BloomOptions *) data; + /* Convert signature length from # of bits to # to words, rounding up */ + opts->bloomLength = (opts->bloomLength + SIGNWORDBITS - 1) / SIGNWORDBITS; +} + + /* * Fill BloomState structure for particular index. */ @@ -470,24 +439,37 @@ BloomInitMetapage(Relation index, ForkNumber forknum) UnlockReleaseBuffer(metaBuffer); } -/* - * Parse reloptions for bloom index, producing a BloomOptions struct. - */ -bytea * -bloptions(Datum reloptions, bool validate) +options_spec_set * +blrelopt_specset(void) { - BloomOptions *rdopts; + int i; + char buf[16]; - /* Parse the user-given reloptions */ - rdopts = (BloomOptions *) build_reloptions(reloptions, validate, - bl_relopt_kind, - sizeof(BloomOptions), - bl_relopt_tab, - lengthof(bl_relopt_tab)); + if (bl_relopt_specset) + return bl_relopt_specset; - /* Convert signature length from # of bits to # to words, rounding up */ - if (rdopts) - rdopts->bloomLength = (rdopts->bloomLength + SIGNWORDBITS - 1) / SIGNWORDBITS; - return (bytea *) rdopts; + bl_relopt_specset = allocateOptionsSpecSet(NULL, + sizeof(BloomOptions), false, INDEX_MAX_KEYS + 1); + bl_relopt_specset->postprocess_fun = blReloptionPostprocess; + + optionsSpecSetAddInt(bl_relopt_specset, "length", + "Length of signature in bits", + NoLock, /* No lock as far as ALTER is not + * effective */ + offsetof(BloomOptions, bloomLength), NULL, + DEFAULT_BLOOM_LENGTH, 1, MAX_BLOOM_LENGTH); + + /* Number of bits for each possible index column: col1, col2, ... */ + for (i = 0; i < INDEX_MAX_KEYS; i++) + { + snprintf(buf, 16, "col%d", i + 1); + optionsSpecSetAddInt(bl_relopt_specset, buf, + "Number of bits for corresponding column", + NoLock, /* No lock as far as ALTER is not + * effective */ + offsetof(BloomOptions, bitSize[i]), NULL, + DEFAULT_BLOOM_BITS, 1, MAX_BLOOM_BITS); + } + return bl_relopt_specset; } diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c index bed2dee3d72..bdd4d83fdf0 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -1938,7 +1938,7 @@ PG_FUNCTION_INFO_V1(dblink_fdw_validator); Datum dblink_fdw_validator(PG_FUNCTION_ARGS) { - List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); + List *options_list = optionsTextArrayToDefList(PG_GETARG_DATUM(0)); Oid context = PG_GETARG_OID(1); ListCell *cell; diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c index 678e754b2b9..043a3667638 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -201,7 +201,7 @@ file_fdw_handler(PG_FUNCTION_ARGS) Datum file_fdw_validator(PG_FUNCTION_ARGS) { - List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); + List *options_list = optionsTextArrayToDefList(PG_GETARG_DATUM(0)); Oid catalog = PG_GETARG_OID(1); char *filename = NULL; DefElem *force_not_null = NULL; diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c index d0766f007d2..085a1228951 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -70,7 +70,7 @@ PG_FUNCTION_INFO_V1(postgres_fdw_validator); Datum postgres_fdw_validator(PG_FUNCTION_ARGS) { - List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); + List *options_list = optionsTextArrayToDefList(PG_GETARG_DATUM(0)); Oid catalog = PG_GETARG_OID(1); ListCell *cell; diff --git a/contrib/test_decoding/expected/twophase.out b/contrib/test_decoding/expected/twophase.out index 08a7c56b5df..98956c5e22f 100644 --- a/contrib/test_decoding/expected/twophase.out +++ b/contrib/test_decoding/expected/twophase.out @@ -69,9 +69,10 @@ WHERE locktype = 'relation' AND relation = 'test_prepared1'::regclass; relation | locktype | mode -----------------+----------+--------------------- + test_prepared_1 | relation | AccessShareLock test_prepared_1 | relation | RowExclusiveLock test_prepared_1 | relation | AccessExclusiveLock -(2 rows) +(3 rows) -- The insert should show the newly altered column but not the DDL. SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml index d17fcbd5cec..af7004dd76c 100644 --- a/doc/src/sgml/indexam.sgml +++ b/doc/src/sgml/indexam.sgml @@ -500,27 +500,12 @@ amgettreeheight (Relation rel); <para> <programlisting> -bytea * -amoptions (ArrayType *reloptions, - bool validate); +void * +amreloptspecset(void); </programlisting> - Parse and validate the reloptions array for an index. This is called only - when a non-null reloptions array exists for the index. - <parameter>reloptions</parameter> is a <type>text</type> array containing entries of the - form <replaceable>name</replaceable><literal>=</literal><replaceable>value</replaceable>. - The function should construct a <type>bytea</type> value, which will be copied - into the <structfield>rd_options</structfield> field of the index's relcache entry. - The data contents of the <type>bytea</type> value are open for the access - method to define; most of the standard access methods use struct - <structname>StdRdOptions</structname>. - When <parameter>validate</parameter> is true, the function should report a suitable - error message if any of the options are unrecognized or have invalid - values; when <parameter>validate</parameter> is false, invalid entries should be - silently ignored. (<parameter>validate</parameter> is false when loading options - already stored in <structname>pg_catalog</structname>; an invalid entry could only - be found if the access method has changed its rules for options, and in - that case ignoring obsolete entries is appropriate.) - It is OK to return NULL if default behavior is wanted. + Returns pointer to Options Spec Set that defines options available for an + index relation. This Spec Set will be used for parsing and validating index + relation options. </para> <para> diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index ccf824bbdb2..abcda91b7bd 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -20,7 +20,6 @@ #include "access/brin_pageops.h" #include "access/brin_xlog.h" #include "access/relation.h" -#include "access/reloptions.h" #include "access/relscan.h" #include "access/table.h" #include "access/tableam.h" @@ -283,7 +282,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; amroutine->amgettreeheight = NULL; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -300,6 +298,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amparallelrescan = NULL; amroutine->amtranslatestrategy = NULL; amroutine->amtranslatecmptype = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1334,23 +1333,6 @@ brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats) return stats; } -/* - * reloptions processor for BRIN indexes - */ -bytea * -brinoptions(Datum reloptions, bool validate) -{ - static const relopt_parse_elt tab[] = { - {"pages_per_range", RELOPT_TYPE_INT, offsetof(BrinOptions, pagesPerRange)}, - {"autosummarize", RELOPT_TYPE_BOOL, offsetof(BrinOptions, autosummarize)} - }; - - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_BRIN, - sizeof(BrinOptions), - tab, lengthof(tab)); -} - /* * SQL-callable function to scan through an index and summarize all ranges * that are not currently summarized. @@ -3009,3 +2991,29 @@ brin_fill_empty_ranges(BrinBuildState *state, blkno += state->bs_pagesPerRange; } } + +static options_spec_set *brin_relopt_specset = NULL; + +options_spec_set * +bringetreloptspecset(void) +{ + if (brin_relopt_specset) + return brin_relopt_specset; + brin_relopt_specset = allocateOptionsSpecSet(NULL, + sizeof(BrinOptions), false, 2); + + optionsSpecSetAddInt(brin_relopt_specset, "pages_per_range", + "Number of pages that each page range covers in a BRIN index", + NoLock, /* since ALTER is not allowed no lock + * needed */ + offsetof(BrinOptions, pagesPerRange), NULL, + BRIN_DEFAULT_PAGES_PER_RANGE, + BRIN_MIN_PAGES_PER_RANGE, + BRIN_MAX_PAGES_PER_RANGE); + optionsSpecSetAddBool(brin_relopt_specset, "autosummarize", + "Enables automatic summarization on this BRIN index", + AccessExclusiveLock, + offsetof(BrinOptions, autosummarize), NULL, + false); + return brin_relopt_specset; +} diff --git a/src/backend/access/common/Makefile b/src/backend/access/common/Makefile index e78de312659..014d1b15622 100644 --- a/src/backend/access/common/Makefile +++ b/src/backend/access/common/Makefile @@ -18,6 +18,7 @@ OBJS = \ detoast.o \ heaptuple.o \ indextuple.o \ + options.o \ printsimple.o \ printtup.o \ relation.o \ diff --git a/src/backend/access/common/meson.build b/src/backend/access/common/meson.build index e3cdbe7a22e..9f9bbc494cc 100644 --- a/src/backend/access/common/meson.build +++ b/src/backend/access/common/meson.build @@ -6,6 +6,7 @@ backend_sources += files( 'detoast.c', 'heaptuple.c', 'indextuple.c', + 'options.c', 'printsimple.c', 'printtup.c', 'relation.c', diff --git a/src/backend/access/common/options.c b/src/backend/access/common/options.c new file mode 100644 index 00000000000..fc42a02b573 --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1367 @@ +/*------------------------------------------------------------------------- + * + * options.c + * An uniform, context-free API for processing name=value options. Used + * to process relation options (reloptions), attribute options, opclass + * options, etc. + * + * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/common/options.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/options.h" +#include "catalog/pg_type.h" +#include "commands/defrem.h" +#include "nodes/makefuncs.h" +#include "utils/builtins.h" +#include "utils/guc.h" +#include "utils/memutils.h" +#include "mb/pg_wchar.h" + + +/* + * OPTIONS SPECIFICATION and OPTION SPECIFICATION SET + * + * Each option is defined via Option Specification object (Option Spec). + * Option Spec should have all information that is needed for processing + * (parsing, validating, converting) of a single option. Implemented via set of + * option_spec_* structures. + * + * A set of Option Specs (Options Spec Set), defines all options available for + * certain object (certain relation kind for example). It is a list of + * Options Specs, plus validation functions that can be used to validate whole + * option set, if needed. Implemented via options_spec_set structure and set of + * optionsSpecSetAdd* functions that are used for adding Option Specs items to + * a Set. + * + * NOTE: we choose term "specification" instead of "definition" because term + * "definition" is used for objects that came from syntax parser. So to avoid + * confusion here we have Option Specifications, and all "definitions" are from + * parser. + */ + +/* + * OPTION VALUES REPRESENTATIONS + * + * Option values usually came from syntax parser in form of defList object, + * stored in pg_catalog as text array, and used when they are stored in memory + * as C-structure. These are different option values representations. Here goes + * brief description of all representations used in the code. + * + * Value List + * + * Value List is an internal representation that is used while converting + * option values between different representation. Value List item is called + * "parsed", when Value's value is converted to a proper data type and + * validated, or is called "unparsed", when Value's value is stored as raw + * string that was obtained from the source without any checks. In conversion + * function names first case is referred as Values, second case is referred as + * RawValues. Value List is implemented as List of option_value C-structures. + * + * defList + * + * Options in form of definition List that comes from syntax parser. (For + * reloptions it is a part of SQL query that goes after WITH, SET or RESET + * keywords). Can be converted to Value List using optionsDefListToRawValues + * and can be obtained from TEXT[] via optionsTextArrayToDefList functions. + * + * TEXT[] + * + * Options in form suitable for storig in TEXT[] field in DB. (E.g. reloptions + * are stores in pg_catalog.pg_class table in reloptions field). Can be + * converted to and from Value List using optionsValuesToTextArray and + * optionsTextArrayToRawValues functions. + * + * Bytea + * + * Option data stored in C-structure with varlena header in the beginning of + * the structure. This representation is used to pass option values to the core + * postgres. It is fast to read, it can be cached and so on. Bytea + * representation can be obtained from Value List using optionsValuesToBytea + * function, and can't be converted back. + */ + +/* + * OPTION STRING VALUE NOTION + * + * Important thing for bytea representation is that all data should be stored + * in one bytea chunk, including values of the string options. This is needed + * as * bytea representation is cached, and can freed, moved or recreated again + * without any notion, so it can't have parts allocated separately. + * + * Thus memory chunk for Bytea option values representation is divided into two + * parts. First goes a C-structure that stores fixed length values. Then goes + * memory area reserved for string values. + * + * For string values C-structure stores offsets (not pointers). These offsets + * can be used to access attached string value: + * + * String_pointer = Bytea_head_pointer + offset. + */ + +static option_spec_basic *allocateOptionSpec(int type, const char *name, + const char *desc, LOCKMODE lockmode, + int struct_offset, bool is_local, + option_value_postvalidate postvalidate_fn); + +static void parse_one_option(option_value *option, bool validate); +static void *optionsAllocateBytea(options_spec_set *spec_set, List *options); + + +static List *optionsDefListToRawValues(List *defList, bool is_for_reset); +static Datum optionsValuesToTextArray(List *options_values); +static List *optionsMergeOptionValues(List *old_options, List *new_options); + +/* + * allocateOptionsSpecSet + * Creates new Options Spec Set object: Allocates memory and initializes + * structure members. + * + * Spec Set items can be add via allocateOptionSpec and optionSpecSetAddItem + * functions or by calling directly any of optionsSpecSetAdd* function + * (preferable way) + * + * namespace - Spec Set can be bind to certain namespace (E.g. + * namespace.option=value). Options from other namespaces will be ignored while + * processing. If set to NULL, no namespace will be used at all. + * + * bytea_size - size of target structure of Bytea options representation + * + * num_items_expected - if you know expected number of Spec Set items set it + * here. Set to -1 in other cases. num_items_expected will be used for + * preallocating memory and will trigger error, if you try to add more items + * than you expected. + */ + +options_spec_set * +allocateOptionsSpecSet(const char *namspace, int bytea_size, bool is_local, + int num_items_expected) +{ + MemoryContext oldcxt; + options_spec_set *spec_set; + + if (!is_local) + oldcxt = MemoryContextSwitchTo(TopMemoryContext); + spec_set = palloc(sizeof(options_spec_set)); + if (namspace) + { + spec_set->namspace = palloc(strlen(namspace) + 1); + strcpy(spec_set->namspace, namspace); + } + else + spec_set->namspace = NULL; + if (num_items_expected > 0) + { + spec_set->num_allocated = num_items_expected; + spec_set->assert_on_realloc = true; + spec_set->definitions = palloc( + spec_set->num_allocated * sizeof(option_spec_basic *)); + } + else + { + spec_set->num_allocated = 0; + spec_set->assert_on_realloc = false; + spec_set->definitions = NULL; + } + spec_set->num = 0; + spec_set->struct_size = bytea_size; + spec_set->postprocess_fun = NULL; + spec_set->is_local = is_local; + if (!is_local) + MemoryContextSwitchTo(oldcxt); + return spec_set; +} + +/* + * allocateOptionSpec + * Allocates a new Option Specifiation object of desired type and + * initialize the type-independent fields + */ +static option_spec_basic * +allocateOptionSpec(int type, const char *name, const char *desc, + LOCKMODE lockmode, int struct_offset, bool is_local, + option_value_postvalidate postvalidate_fn) +{ + MemoryContext oldcxt; + size_t size; + option_spec_basic *newoption; + + if (!is_local) + oldcxt = MemoryContextSwitchTo(TopMemoryContext); + + switch (type) + { + case OPTION_TYPE_BOOL: + size = sizeof(option_spec_bool); + break; + case OPTION_TYPE_INT: + size = sizeof(option_spec_int); + break; + case OPTION_TYPE_REAL: + size = sizeof(option_spec_real); + break; + case OPTION_TYPE_ENUM: + size = sizeof(option_spec_enum); + break; + case OPTION_TYPE_STRING: + size = sizeof(option_spec_string); + break; + default: + elog(ERROR, "unsupported reloption type %d", (int) type); + return NULL; /* keep compiler quiet */ + } + + newoption = palloc(size); + + newoption->name = pstrdup(name); + if (desc) + newoption->desc = pstrdup(desc); + else + newoption->desc = NULL; + newoption->type = type; + newoption->lockmode = lockmode; + newoption->struct_offset = struct_offset; + newoption->postvalidate_fn = postvalidate_fn; + + if (!is_local) + MemoryContextSwitchTo(oldcxt); + + return newoption; +} + +/* + * optionSpecSetAddItem + * Adds pre-created Option Specification object to the Spec Set + */ +static void +optionSpecSetAddItem(option_spec_basic *newoption, + options_spec_set *spec_set) +{ + if (spec_set->num >= spec_set->num_allocated) + { + MemoryContext oldcxt = NULL; + + Assert(!spec_set->assert_on_realloc); + if (!spec_set->is_local) + oldcxt = MemoryContextSwitchTo(TopMemoryContext); + + if (spec_set->num_allocated == 0) + { + spec_set->num_allocated = 8; + spec_set->definitions = palloc( + spec_set->num_allocated * sizeof(option_spec_basic *)); + } + else + { + spec_set->num_allocated *= 2; + spec_set->definitions = repalloc(spec_set->definitions, + spec_set->num_allocated * sizeof(option_spec_basic *)); + } + if (!spec_set->is_local) + MemoryContextSwitchTo(oldcxt); + } + spec_set->definitions[spec_set->num] = newoption; + spec_set->num++; +} + + +/* + * optionsSpecSetAddBool + * Adds boolean Option Specification entry to the Spec Set + */ +void +optionsSpecSetAddBool(options_spec_set *spec_set, const char *name, + const char *desc, LOCKMODE lockmode, int struct_offset, + option_value_postvalidate postvalidate_fn, + bool default_val) +{ + option_spec_bool *spec_set_item; + + spec_set_item = (option_spec_bool *) allocateOptionSpec(OPTION_TYPE_BOOL, + name, desc, lockmode, struct_offset, + spec_set->is_local, postvalidate_fn); + + spec_set_item->default_val = default_val; + + optionSpecSetAddItem((option_spec_basic *) spec_set_item, spec_set); +} + +/* + * optionsSpecSetAddInt + * Adds integer Option Specification entry to the Spec Set + */ +void +optionsSpecSetAddInt(options_spec_set *spec_set, const char *name, + const char *desc, LOCKMODE lockmode, int struct_offset, + option_value_postvalidate postvalidate_fn, + int default_val, int min_val, int max_val) +{ + option_spec_int *spec_set_item; + + spec_set_item = (option_spec_int *) allocateOptionSpec(OPTION_TYPE_INT, + name, desc, lockmode, struct_offset, + spec_set->is_local, postvalidate_fn); + + spec_set_item->default_val = default_val; + spec_set_item->min = min_val; + spec_set_item->max = max_val; + + optionSpecSetAddItem((option_spec_basic *) spec_set_item, spec_set); +} + +/* + * optionsSpecSetAddReal + * Adds float Option Specification entry to the Spec Set + */ +void +optionsSpecSetAddReal(options_spec_set *spec_set, const char *name, + const char *desc, LOCKMODE lockmode, int struct_offset, + option_value_postvalidate postvalidate_fn, + double default_val, double min_val, double max_val) +{ + option_spec_real *spec_set_item; + + spec_set_item = (option_spec_real *) allocateOptionSpec(OPTION_TYPE_REAL, + name, desc, lockmode, struct_offset, + spec_set->is_local, postvalidate_fn); + + spec_set_item->default_val = default_val; + spec_set_item->min = min_val; + spec_set_item->max = max_val; + + optionSpecSetAddItem((option_spec_basic *) spec_set_item, spec_set); +} + +/* + * optionsSpecSetAddEnum + * Adds enum Option Specification entry to the Spec Set + * + * The members array must have a terminating NULL entry. + * + * The detailmsg is shown when unsupported values are passed, and has this + * form: "Valid values are \"foo\", \"bar\", and \"bar\"." + * + * The members array and detailmsg are not copied -- caller must ensure that + * they are valid throughout the life of the process. + */ + +void +optionsSpecSetAddEnum(options_spec_set *spec_set, const char *name, + const char *desc, LOCKMODE lockmode, int struct_offset, + option_value_postvalidate postvalidate_fn, + opt_enum_elt_def *members, int default_val, + const char *detailmsg) +{ + option_spec_enum *spec_set_item; + + spec_set_item = (option_spec_enum *) allocateOptionSpec(OPTION_TYPE_ENUM, + name, desc, lockmode, struct_offset, + spec_set->is_local, postvalidate_fn); + + spec_set_item->default_val = default_val; + spec_set_item->members = members; + spec_set_item->detailmsg = detailmsg; + + optionSpecSetAddItem((option_spec_basic *) spec_set_item, spec_set); +} + +/* + * optionsSpecSetAddString + * Adds string Option Specification entry to the Spec Set + * + * "validator" is an optional function pointer that can be used to test the + * validity of the values. It must elog(ERROR) when the argument string is + * not acceptable for the variable. Note that the default value must pass + * the validation. + */ +void +optionsSpecSetAddString(options_spec_set *spec_set, const char *name, + const char *desc, LOCKMODE lockmode, int struct_offset, + option_value_postvalidate postvalidate_fn, + const char *default_val, validate_string_option validator, + fill_string_option filler) +{ + option_spec_string *spec_set_item; + + /* make sure the validator/default combination is sane */ + if (validator) + (validator) (default_val); + + spec_set_item = (option_spec_string *) allocateOptionSpec( + OPTION_TYPE_STRING, + name, desc, lockmode, struct_offset, + spec_set->is_local, postvalidate_fn); + spec_set_item->validate_cb = validator; + spec_set_item->fill_cb = filler; + + if (default_val) + spec_set_item->default_val = MemoryContextStrdup(TopMemoryContext, + default_val); + else + spec_set_item->default_val = NULL; + optionSpecSetAddItem((option_spec_basic *) spec_set_item, spec_set); +} + +/* optionsDefListToRawValues + * Converts options values from DefList representation into Raw Values + * List. + * + * No parsing is done here except for checking that RESET syntax is correct + * (i.e. does not have =name part of value=name template). Syntax analyzer does + * not see difference between SET and RESET cases, so we should treat it here + * manually + */ +static List * +optionsDefListToRawValues(List *defList, bool is_for_reset) +{ + ListCell *cell; + List *result = NIL; + + foreach(cell, defList) + { + option_value *option_dst; + DefElem *def = (DefElem *) lfirst(cell); + char *value; + + option_dst = palloc(sizeof(option_value)); + + if (def->defnamespace) + { + option_dst->namspace = palloc(strlen(def->defnamespace) + 1); + strcpy(option_dst->namspace, def->defnamespace); + } + else + option_dst->namspace = NULL; + + option_dst->raw_name = palloc(strlen(def->defname) + 1); + strcpy(option_dst->raw_name, def->defname); + + if (is_for_reset) + { + /* + * If this option came from RESET statement we should throw error + * it it brings us name=value data, as syntax analyzer do not + * prevent it + */ + if (def->arg != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("RESET must not include values for parameters"))); + + option_dst->status = OPTION_VALUE_STATUS_FOR_RESET; + } + else + { + /* + * For SET statement we should treat (name) expression as if it is + * actually (name=true) so do it here manually. In other cases + * just use value as we should use it + */ + option_dst->status = OPTION_VALUE_STATUS_RAW; + if (def->arg != NULL) + value = defGetString(def); + else + value = "true"; + option_dst->raw_value = palloc(strlen(value) + 1); + strcpy(option_dst->raw_value, value); + } + + result = lappend(result, option_dst); + } + return result; +} + +/* + * optionsValuesToTextArray + * Converts options Values List (option_values) into TEXT[] representation + * + * This conversion is usually needed for saving option values into database + * (e.g. to store reloptions in pg_class.reloptions) + */ + +Datum +optionsValuesToTextArray(List *options_values) +{ + ArrayBuildState *astate = NULL; + ListCell *cell; + Datum result; + + foreach(cell, options_values) + { + option_value *option = (option_value *) lfirst(cell); + const char *name; + char *value; + text *t; + int len; + + /* + * Raw value were not cleared while parsing, so instead of converting + * it back, just use it to store value as text + */ + value = option->raw_value; + + Assert(option->status != OPTION_VALUE_STATUS_EMPTY); + + /* + * Name will be taken from option definition, if option were parsed or + * from raw_name if option were not parsed for some reason + */ + if (option->status == OPTION_VALUE_STATUS_PARSED) + name = option->gen->name; + else + name = option->raw_name; + + /* + * Now build "name=value" string and append it to the array + */ + len = VARHDRSZ + strlen(name) + strlen(value) + 1; + t = (text *) palloc(len + 1); + SET_VARSIZE(t, len); + sprintf(VARDATA(t), "%s=%s", name, value); + astate = accumArrayResult(astate, PointerGetDatum(t), false, + TEXTOID, CurrentMemoryContext); + } + if (astate) + result = makeArrayResult(astate, CurrentMemoryContext); + else + result = (Datum) 0; + + return result; +} + +/* + * optionsTextArrayToRawValues + * Converts option values from TEXT[] representation (datum_array) into + * Raw Values List. + * + * Used while fetching options values from DB, as a first step of converting + * them to other representations. + */ +List * +optionsTextArrayToRawValues(Datum array_datum) +{ + List *result = NIL; + + if (PointerIsValid(DatumGetPointer(array_datum))) + { + ArrayType *array = DatumGetArrayTypeP(array_datum); + Datum *options; + int noptions; + int i; + + deconstruct_array_builtin(array, TEXTOID, &options, NULL, &noptions); + + for (i = 0; i < noptions; i++) + { + option_value *option_dst; + char *text_str = VARDATA(options[i]); + int text_len = VARSIZE(options[i]) - VARHDRSZ; + int j; + int name_len = -1; + char *name; + int raw_value_len; + char *raw_value; + + /* + * Find position of '=' sign and treat id as a separator between + * name and value in "name=value" item + */ + for (j = 0; j < text_len; j = j + pg_mblen(text_str)) + { + if (text_str[j] == '=') + { + name_len = j; + break; + } + } + Assert(name_len >= 1); /* Just in case */ + + raw_value_len = text_len - name_len - 1; + + /* + * Copy name from src + */ + name = palloc(name_len + 1); + memcpy(name, text_str, name_len); + name[name_len] = '\0'; + + /* + * Copy value from src + */ + raw_value = palloc(raw_value_len + 1); + memcpy(raw_value, text_str + name_len + 1, raw_value_len); + raw_value[raw_value_len] = '\0'; + + /* + * Create new option_value item + */ + option_dst = palloc(sizeof(option_value)); + option_dst->status = OPTION_VALUE_STATUS_RAW; + option_dst->raw_name = name; + option_dst->raw_value = raw_value; + option_dst->namspace = NULL; + + result = lappend(result, option_dst); + } + } + return result; +} + +/* + * optionsMergeOptionValues + * Updates(or Resets) values from one Options Values List(old_options), + * with values from another Options Values List (new_options) + + * This function is used while ALTERing options of some object. + * If option from new_options list has OPTION_VALUE_STATUS_FOR_RESET flag + * on, option with that name will be excluded result list. + */ +static List * +optionsMergeOptionValues(List *old_options, List *new_options) +{ + List *result = NIL; + ListCell *old_cell; + ListCell *new_cell; + + /* + * First add to result all old options that are not mentioned in new list + */ + foreach(old_cell, old_options) + { + bool found; + const char *old_name; + option_value *old_option; + + old_option = (option_value *) lfirst(old_cell); + if (old_option->status == OPTION_VALUE_STATUS_PARSED) + old_name = old_option->gen->name; + else + old_name = old_option->raw_name; + + /* + * Looking for a new option with same name + */ + found = false; + foreach(new_cell, new_options) + { + option_value *new_option; + const char *new_name; + + new_option = (option_value *) lfirst(new_cell); + if (new_option->status == OPTION_VALUE_STATUS_PARSED) + new_name = new_option->gen->name; + else + new_name = new_option->raw_name; + + if (strcmp(new_name, old_name) == 0) + { + found = true; + break; + } + } + if (!found) + result = lappend(result, old_option); + } + + /* + * Now add all to result all new options that are not designated for reset + */ + foreach(new_cell, new_options) + { + option_value *new_option; + + new_option = (option_value *) lfirst(new_cell); + + if (new_option->status != OPTION_VALUE_STATUS_FOR_RESET) + result = lappend(result, new_option); + } + return result; +} + +/* + * optionsDefListValdateNamespaces + * Checks that defList has only options with namespaces from + * allowed_namspaces array. Items without namspace are also accepted + * + * Used while validation of syntax parser output. Error is thrown if unproper + * namespace is found. + */ +void +optionsDefListValdateNamespaces(List *defList, + const char * const* allowed_namspaces) +{ + ListCell *cell; + + foreach(cell, defList) + { + DefElem *def = (DefElem *) lfirst(cell); + + /* + * Checking namespace only for options that have namespaces. Options + * with no namespaces are always accepted + */ + if (def->defnamespace) + { + bool found = false; + int i = 0; + + while (allowed_namspaces[i]) + { + if (strcmp(def->defnamespace, + allowed_namspaces[i]) == 0) + { + found = true; + break; + } + i++; + } + if (!found) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter namespace \"%s\"", + def->defnamespace))); + } + } +} + +/* + * optionsDefListFilterNamespaces + * Filter out DefList items that has "namspace" namespace. If "namspace" + * is NULL, only namespaseless options are returned + */ +List * +optionsDefListFilterNamespaces(List *defList, const char *namspace) +{ + ListCell *cell; + List *result = NIL; + + foreach(cell, defList) + { + DefElem *def = (DefElem *) lfirst(cell); + + if ((!namspace && !def->defnamespace) || + (namspace && def->defnamespace && + strcmp(namspace, def->defnamespace) == 0)) + result = lappend(result, def); + } + return result; +} + +/* + * optionsTextArrayToDefList + * Converts option values from TEXT[] representation into DefList + * representation. + */ +List * +optionsTextArrayToDefList(Datum options) +{ + List *result = NIL; + ArrayType *array; + Datum *optiondatums; + int noptions; + int i; + + /* Nothing to do if no options */ + if (!PointerIsValid(DatumGetPointer(options))) + return result; + + array = DatumGetArrayTypeP(options); + + deconstruct_array_builtin(array, TEXTOID, &optiondatums, NULL, &noptions); + + for (i = 0; i < noptions; i++) + { + char *s; + char *p; + Node *val = NULL; + + s = TextDatumGetCString(optiondatums[i]); + p = strchr(s, '='); + if (p) + { + *p++ = '\0'; + val = (Node *) makeString(pstrdup(p)); + } + result = lappend(result, makeDefElem(pstrdup(s), val, -1)); + } + + return result; +} + +/* + * optionsDefListToTextArray + * Converts option values from DefList representation into TEXT[] + * representation. + */ +Datum +optionsDefListToTextArray(List *defList) +{ + ListCell *cell; + Datum result; + ArrayBuildState *astate = NULL; + + foreach(cell, defList) + { + DefElem *def = (DefElem *) lfirst(cell); + const char *name = def->defname; + const char *value; + text *t; + int len; + + if (def->arg != NULL) + value = defGetString(def); + else + value = "true"; + + if (def->defnamespace) + { + /* + * This function is used for backward compatibility in the place + * where namespases are not allowed + */ + Assert(false); /* Should not get here */ + return (Datum) 0; + } + len = VARHDRSZ + strlen(name) + strlen(value) + 1; + t = (text *) palloc(len + 1); + SET_VARSIZE(t, len); + sprintf(VARDATA(t), "%s=%s", name, value); + astate = accumArrayResult(astate, PointerGetDatum(t), false, + TEXTOID, CurrentMemoryContext); + + } + if (astate) + result = makeArrayResult(astate, CurrentMemoryContext); + else + result = (Datum) 0; + return result; +} + + +/* + * optionsParseRawValues + * Transforms RawValues List into [Parsed] Values List. Validation is done + * if validate flag is set. + * + * Options data that come parsed SQL query or DB storage, first converted into + * RawValues (where value are kept in text format), then is parsed into + * Values using this function + * + * Validation is used only for data that came from SQL query. We trust that + * data that came from DB is correct. + * + * If validation is off, all unknown options are kept unparsed so they will + * be stored back to DB until user RESETs them directly. + * + * This function destroys incoming list. + */ +List * +optionsParseRawValues(List *raw_values, options_spec_set *spec_set, + bool validate) +{ + ListCell *cell; + List *result = NIL; + bool *is_set; + int i; + + is_set = palloc0(sizeof(bool) * spec_set->num); + foreach(cell, raw_values) + { + option_value *option = (option_value *) lfirst(cell); + bool found = false; + + /* option values with RESET status does not need parsing */ + Assert(option->status != OPTION_VALUE_STATUS_FOR_RESET); + + /* Should not parse Values Set twice */ + Assert(option->status != OPTION_VALUE_STATUS_PARSED); + + if (validate && option->namspace && (!spec_set->namspace || + strcmp(spec_set->namspace, option->namspace) != 0)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter namespace \"%s\"", + option->namspace))); + + for (i = 0; i < spec_set->num; i++) + { + option_spec_basic *opt_spec = spec_set->definitions[i]; + + if (strcmp(option->raw_name, opt_spec->name) == 0) + { + if (validate && is_set[i]) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter \"%s\" specified more than once", + option->raw_name))); + + pfree(option->raw_name); + option->raw_name = NULL; + option->gen = opt_spec; + parse_one_option(option, validate); + is_set[i] = true; + found = true; + break; + } + } + if (validate && !found) + { + if (option->namspace) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter \"%s.%s\"", + option->namspace, option->raw_name))); + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter \"%s\"", + option->raw_name))); + } + result = lappend(result, option); + } + return result; +} + +/* + * parse_one_option + * Function to parse and validate single option. + * + * See optionsParseRawValues for more info. + * + * Link to Option Spec for the option is embedded into "option_value" + */ +static void +parse_one_option(option_value *option, bool validate) +{ + char *value; + bool parsed; + + value = option->raw_value; + + switch (option->gen->type) + { + case OPTION_TYPE_BOOL: + { + parsed = parse_bool(value, &option->values.bool_val); + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for boolean option \"%s\": %s", + option->gen->name, value))); + } + break; + case OPTION_TYPE_INT: + { + option_spec_int *optint = + (option_spec_int *) option->gen; + + parsed = parse_int(value, &option->values.int_val, 0, NULL); + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for integer option \"%s\": %s", + option->gen->name, value))); + if (validate && (option->values.int_val < optint->min || + option->values.int_val > optint->max)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("value %s out of bounds for option \"%s\"", + value, option->gen->name), + errdetail("Valid values are between \"%d\" and \"%d\".", + optint->min, optint->max))); + } + break; + case OPTION_TYPE_REAL: + { + option_spec_real *optreal = + (option_spec_real *) option->gen; + + parsed = parse_real(value, &option->values.real_val, 0, NULL); + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for floating point option \"%s\": %s", + option->gen->name, value))); + if (validate && (option->values.real_val < optreal->min || + option->values.real_val > optreal->max)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("value %s out of bounds for option \"%s\"", + value, option->gen->name), + errdetail("Valid values are between \"%f\" and \"%f\".", + optreal->min, optreal->max))); + } + break; + case OPTION_TYPE_ENUM: + { + option_spec_enum *optenum = + (option_spec_enum *) option->gen; + opt_enum_elt_def *elt; + + parsed = false; + for (elt = optenum->members; elt->string_val; elt++) + { + if (strcmp(value, elt->string_val) == 0) + { + option->values.enum_val = elt->symbol_val; + parsed = true; + break; + } + } + if (!parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for enum option \"%s\": %s", + option->gen->name, value), + optenum->detailmsg ? + errdetail_internal("%s", _(optenum->detailmsg)) : 0)); + } + break; + case OPTION_TYPE_STRING: + { + option_spec_string *optstring = + (option_spec_string *) option->gen; + + option->values.string_val = value; + if (validate && optstring->validate_cb) + (optstring->validate_cb) (value); + parsed = true; + } + break; + default: + elog(ERROR, "unsupported reloption type %d", (int) option->gen->type); + parsed = true; /* quiet compiler */ + break; + } + if (validate && option->gen->postvalidate_fn) + option->gen->postvalidate_fn(option); + + if (parsed) + option->status = OPTION_VALUE_STATUS_PARSED; + +} + +/* + * optionsAllocateBytea + * Allocates memory for Bytea options representation + * + * We need special function for this, as string option values are embedded into + * Bytea object, stored at the rear part of the memory chunk. Thus we need to + * allocate extra memory, so all string values would fit in. This function + * calculates required size and do allocation. + * + * See "OPTION STRING VALUE NOTION" at the beginning of the file for better + * understanding. + */ +static void * +optionsAllocateBytea(options_spec_set *spec_set, List *options) +{ + Size size; + int i; + ListCell *cell; + int length; + void *res; + + size = spec_set->struct_size; + + /* Calculate size needed to store all string values for this option */ + for (i = 0; i < spec_set->num; i++) + { + option_spec_basic *opt_spec = spec_set->definitions[i]; + option_spec_string *opt_spec_str; + bool found = false; + option_value *option; + const char *val = NULL; + + /* Not interested in non-string options, skipping */ + if (opt_spec->type != OPTION_TYPE_STRING) + continue; + + /* + * Trying to find option_value that references opt_spec entry + */ + opt_spec_str = (option_spec_string *) opt_spec; + foreach(cell, options) + { + option = (option_value *) lfirst(cell); + if (option->status == OPTION_VALUE_STATUS_PARSED && + strcmp(option->gen->name, opt_spec->name) == 0) + { + found = true; + break; + } + } + if (found) + val = option->values.string_val; + else + val = opt_spec_str->default_val; + + if (opt_spec_str->fill_cb) + length = opt_spec_str->fill_cb(val, NULL); + else if (val) + length = strlen(val) + 1; + else + length = 0; /* "Default Value is NULL" case */ + + /* Add total length of each string values to basic size */ + size += length; + } + + res = palloc0(size); + SET_VARSIZE(res, size); + return res; +} + +/* + * optionsValuesToBytea + * Converts options values from Value List representation to Bytea + * representation. + * + * Fills resulting Bytea with option values according to Option Sec Set. + * + * For understanding processing of the string option please read "OPTION STRING + * VALUE NOTION" at the beginning of the file. + */ +bytea * +optionsValuesToBytea(List *options, options_spec_set *spec_set) +{ + char *data; + char *string_values_buffer; + int i; + + data = optionsAllocateBytea(spec_set, options); + + /* place for string data starts right after original structure */ + string_values_buffer = data + spec_set->struct_size; + + for (i = 0; i < spec_set->num; i++) + { + option_value *found = NULL; + ListCell *cell; + char *item_pos; + option_spec_basic *opt_spec = spec_set->definitions[i]; + + if (opt_spec->struct_offset < 0) + continue; /* This option value should not be stored in + * Bytea for some reason. May be it is + * deprecated and has warning or error in + * postvalidate function */ + + /* Calculate the position of the item inside the structure */ + item_pos = data + opt_spec->struct_offset; + + /* Looking for the corresponding option from options list */ + foreach(cell, options) + { + option_value *option = (option_value *) lfirst(cell); + + if (option->status == OPTION_VALUE_STATUS_RAW) + continue; /* raw can come from db. Just ignore them then */ + Assert(option->status != OPTION_VALUE_STATUS_EMPTY); + + if (strcmp(opt_spec->name, option->gen->name) == 0) + { + found = option; + break; + } + } + /* writing to the proper position either option value or default val */ + switch (opt_spec->type) + { + case OPTION_TYPE_BOOL: + *(bool *) item_pos = found ? + found->values.bool_val : + ((option_spec_bool *) opt_spec)->default_val; + break; + case OPTION_TYPE_INT: + *(int *) item_pos = found ? + found->values.int_val : + ((option_spec_int *) opt_spec)->default_val; + break; + case OPTION_TYPE_REAL: + *(double *) item_pos = found ? + found->values.real_val : + ((option_spec_real *) opt_spec)->default_val; + break; + case OPTION_TYPE_ENUM: + *(int *) item_pos = found ? + found->values.enum_val : + ((option_spec_enum *) opt_spec)->default_val; + break; + + case OPTION_TYPE_STRING: + { + /* + * For string options: writing string value at the string + * buffer after the structure, and storing and offset to + * that value + */ + char *value = NULL; + option_spec_string *opt_spec_str = + (option_spec_string *) opt_spec; + + if (found) + value = found->values.string_val; + else + value = opt_spec_str->default_val; + + if (opt_spec_str->fill_cb) + { + Size size = + opt_spec_str->fill_cb(value, string_values_buffer); + + if (size) + { + *(int *) item_pos = string_values_buffer - data; + string_values_buffer += size; + } + else + *(int *) item_pos = + OPTION_STRING_VALUE_NOT_SET_OFFSET; + } + else + { + *(int *) item_pos = value ? + string_values_buffer - data : + OPTION_STRING_VALUE_NOT_SET_OFFSET; + if (value) + { + strcpy(string_values_buffer, value); + string_values_buffer += strlen(value) + 1; + } + } + } + break; + default: + elog(ERROR, "unsupported reloption type %d", + (int) opt_spec->type); + break; + } + } + return (void *) data; +} + +/* + * optionDefListToTextArray + * Converts options from defList to TEXT[] representation. + * + * Used when new relation (or other object) is created. defList comes from + * SQL syntax parser, TEXT[] goes to DB storage. Options are always validated + * while conversion. + */ +Datum +optionDefListToTextArray(options_spec_set *spec_set, List *defList) +{ + Datum result; + List *new_values; + + /* Parse and validate new values */ + new_values = optionsDefListToRawValues(defList, false); + new_values = optionsParseRawValues(new_values, spec_set, true); + + /* Some checks can be done in postprocess_fun, we should call it */ + if (spec_set->postprocess_fun) + { + bytea *data; + if (defList) + data = optionsValuesToBytea(new_values, spec_set); + else + data = NULL; + + spec_set->postprocess_fun(data, true); + if (data) pfree(data); + } + result = optionsValuesToTextArray(new_values); + return result; +} + +/* + * optionsUpdateTexArrayWithDefList + * Modifies oldOptions values (in TEXT[] representation) with defList + * values. + * + * Old values are appened or replaced with new values if do_reset flag is set + * to false. If do_reset is set to true, defList specify the list of the options + * that should be removed from original list. + */ + +Datum +optionsUpdateTexArrayWithDefList(options_spec_set *spec_set, Datum oldOptions, + List *defList, bool do_reset) +{ + Datum result; + List *new_values; + List *old_values; + List *merged_values; + + /* + * Parse and validate New values + */ + new_values = optionsDefListToRawValues(defList, do_reset); + if (!do_reset) + new_values = optionsParseRawValues(new_values, spec_set, true); + + if (PointerIsValid(DatumGetPointer(oldOptions))) + { + old_values = optionsTextArrayToRawValues(oldOptions); + merged_values = optionsMergeOptionValues(old_values, new_values); + } + else + { + if (do_reset) + merged_values = NULL; /* return nothing */ + else + merged_values = new_values; + } + + /* + * If we have postprocess_fun function defined in spec_set, then there + * might be some custom options checks there, with error throwing. So we + * should do it here to throw these errors while CREATing or ALTERing + * options + */ + if (spec_set->postprocess_fun) + { + bytea *data = optionsValuesToBytea(merged_values, spec_set); + + spec_set->postprocess_fun(data, true); + pfree(data); + } + + /* + * Convert options to TextArray format so caller can store them into + * database + */ + result = optionsValuesToTextArray(merged_values); + return result; +} + +/* + * optionsTextArrayToBytea + * Convert options values from TEXT[] representation into Bytea + * representation + * + * This function uses other conversion function to get desired result. + */ +bytea * +optionsTextArrayToBytea(options_spec_set *spec_set, Datum data, bool validate) +{ + List *values; + bytea *options; + + values = optionsTextArrayToRawValues(data); + values = optionsParseRawValues(values, spec_set, validate); + options = optionsValuesToBytea(values, spec_set); + + if (spec_set->postprocess_fun) + spec_set->postprocess_fun(options, false); + return options; +} diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 5731cf42f54..a5a78d092dc 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -1,7 +1,7 @@ /*------------------------------------------------------------------------- * * reloptions.c - * Core support for relation options (pg_class.reloptions) + * Support for relation options (pg_class.reloptions) * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California @@ -17,13 +17,10 @@ #include <float.h> -#include "access/gist_private.h" -#include "access/hash.h" #include "access/heaptoast.h" #include "access/htup_details.h" -#include "access/nbtree.h" #include "access/reloptions.h" -#include "access/spgist_private.h" +#include "access/options.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "commands/tablespace.h" @@ -34,6 +31,7 @@ #include "utils/guc.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "storage/bufmgr.h" /* * Contents of pg_class.reloptions @@ -91,398 +89,8 @@ * value has no effect until the next VACUUM, so no need for stronger lock. */ -static relopt_bool boolRelOpts[] = -{ - { - { - "autosummarize", - "Enables automatic summarization on this BRIN index", - RELOPT_KIND_BRIN, - AccessExclusiveLock - }, - false - }, - { - { - "autovacuum_enabled", - "Enables autovacuum in this relation", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, - { - { - "user_catalog_table", - "Declare a table as an additional catalog table, e.g. for the purpose of logical replication", - RELOPT_KIND_HEAP, - AccessExclusiveLock - }, - false - }, - { - { - "fastupdate", - "Enables \"fast update\" feature for this GIN index", - RELOPT_KIND_GIN, - AccessExclusiveLock - }, - true - }, - { - { - "security_barrier", - "View acts as a row security barrier", - RELOPT_KIND_VIEW, - AccessExclusiveLock - }, - false - }, - { - { - "security_invoker", - "Privileges on underlying relations are checked as the invoking user, not the view owner", - RELOPT_KIND_VIEW, - AccessExclusiveLock - }, - false - }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, - { - { - "deduplicate_items", - "Enables \"deduplicate items\" feature for this btree index", - RELOPT_KIND_BTREE, - ShareUpdateExclusiveLock /* since it applies only to later - * inserts */ - }, - true - }, - /* list terminator */ - {{NULL}} -}; - -static relopt_int intRelOpts[] = -{ - { - { - "fillfactor", - "Packs table pages only to this percentage", - RELOPT_KIND_HEAP, - ShareUpdateExclusiveLock /* since it applies only to later - * inserts */ - }, - HEAP_DEFAULT_FILLFACTOR, HEAP_MIN_FILLFACTOR, 100 - }, - { - { - "fillfactor", - "Packs btree index pages only to this percentage", - RELOPT_KIND_BTREE, - ShareUpdateExclusiveLock /* since it applies only to later - * inserts */ - }, - BTREE_DEFAULT_FILLFACTOR, BTREE_MIN_FILLFACTOR, 100 - }, - { - { - "fillfactor", - "Packs hash index pages only to this percentage", - RELOPT_KIND_HASH, - ShareUpdateExclusiveLock /* since it applies only to later - * inserts */ - }, - HASH_DEFAULT_FILLFACTOR, HASH_MIN_FILLFACTOR, 100 - }, - { - { - "fillfactor", - "Packs gist index pages only to this percentage", - RELOPT_KIND_GIST, - ShareUpdateExclusiveLock /* since it applies only to later - * inserts */ - }, - GIST_DEFAULT_FILLFACTOR, GIST_MIN_FILLFACTOR, 100 - }, - { - { - "fillfactor", - "Packs spgist index pages only to this percentage", - RELOPT_KIND_SPGIST, - ShareUpdateExclusiveLock /* since it applies only to later - * inserts */ - }, - SPGIST_DEFAULT_FILLFACTOR, SPGIST_MIN_FILLFACTOR, 100 - }, - { - { - "autovacuum_vacuum_threshold", - "Minimum number of tuple updates or deletes prior to vacuum", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, 0, INT_MAX - }, - { - { - "autovacuum_vacuum_max_threshold", - "Maximum number of tuple updates or deletes prior to vacuum", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -2, -1, INT_MAX - }, - { - { - "autovacuum_vacuum_insert_threshold", - "Minimum number of tuple inserts prior to vacuum, or -1 to disable insert vacuums", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -2, -1, INT_MAX - }, - { - { - "autovacuum_analyze_threshold", - "Minimum number of tuple inserts, updates or deletes prior to analyze", - RELOPT_KIND_HEAP, - ShareUpdateExclusiveLock - }, - -1, 0, INT_MAX - }, - { - { - "autovacuum_vacuum_cost_limit", - "Vacuum cost amount available before napping, for autovacuum", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, 1, 10000 - }, - { - { - "autovacuum_freeze_min_age", - "Minimum age at which VACUUM should freeze a table row, for autovacuum", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, 0, 1000000000 - }, - { - { - "autovacuum_multixact_freeze_min_age", - "Minimum multixact age at which VACUUM should freeze a row multixact's, for autovacuum", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, 0, 1000000000 - }, - { - { - "autovacuum_freeze_max_age", - "Age at which to autovacuum a table to prevent transaction ID wraparound", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, 100000, 2000000000 - }, - { - { - "autovacuum_multixact_freeze_max_age", - "Multixact age at which to autovacuum a table to prevent multixact wraparound", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, 10000, 2000000000 - }, - { - { - "autovacuum_freeze_table_age", - "Age at which VACUUM should perform a full table sweep to freeze row versions", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, -1, 0, 2000000000 - }, - { - { - "autovacuum_multixact_freeze_table_age", - "Age of multixact at which VACUUM should perform a full table sweep to freeze row versions", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, -1, 0, 2000000000 - }, - { - { - "log_autovacuum_min_duration", - "Sets the minimum execution time above which autovacuum actions will be logged", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, -1, INT_MAX - }, - { - { - "toast_tuple_target", - "Sets the target tuple length at which external columns will be toasted", - RELOPT_KIND_HEAP, - ShareUpdateExclusiveLock - }, - TOAST_TUPLE_TARGET, 128, TOAST_TUPLE_TARGET_MAIN - }, - { - { - "pages_per_range", - "Number of pages that each page range covers in a BRIN index", - RELOPT_KIND_BRIN, - AccessExclusiveLock - }, 128, 1, 131072 - }, - { - { - "gin_pending_list_limit", - "Maximum size of the pending list for this GIN index, in kilobytes.", - RELOPT_KIND_GIN, - AccessExclusiveLock - }, - -1, 64, MAX_KILOBYTES - }, - { - { - "effective_io_concurrency", - "Number of simultaneous requests that can be handled efficiently by the disk subsystem.", - RELOPT_KIND_TABLESPACE, - ShareUpdateExclusiveLock - }, -#ifdef USE_PREFETCH - -1, 0, MAX_IO_CONCURRENCY -#else - 0, 0, 0 -#endif - }, - { - { - "maintenance_io_concurrency", - "Number of simultaneous requests that can be handled efficiently by the disk subsystem for maintenance work.", - RELOPT_KIND_TABLESPACE, - ShareUpdateExclusiveLock - }, -#ifdef USE_PREFETCH - -1, 0, MAX_IO_CONCURRENCY -#else - 0, 0, 0 -#endif - }, - { - { - "parallel_workers", - "Number of parallel processes that can be used per executor node for this relation.", - RELOPT_KIND_HEAP, - ShareUpdateExclusiveLock - }, - -1, 0, 1024 - }, - - /* list terminator */ - {{NULL}} -}; - -static relopt_real realRelOpts[] = -{ - { - { - "autovacuum_vacuum_cost_delay", - "Vacuum cost delay in milliseconds, for autovacuum", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, 0.0, 100.0 - }, - { - { - "autovacuum_vacuum_scale_factor", - "Number of tuple updates or deletes prior to vacuum as a fraction of reltuples", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, 0.0, 100.0 - }, - { - { - "autovacuum_vacuum_insert_scale_factor", - "Number of tuple inserts prior to vacuum as a fraction of reltuples", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, 0.0, 100.0 - }, - { - { - "autovacuum_analyze_scale_factor", - "Number of tuple inserts, updates or deletes prior to analyze as a fraction of reltuples", - RELOPT_KIND_HEAP, - ShareUpdateExclusiveLock - }, - -1, 0.0, 100.0 - }, - { - { - "seq_page_cost", - "Sets the planner's estimate of the cost of a sequentially fetched disk page.", - RELOPT_KIND_TABLESPACE, - ShareUpdateExclusiveLock - }, - -1, 0.0, DBL_MAX - }, - { - { - "random_page_cost", - "Sets the planner's estimate of the cost of a nonsequentially fetched disk page.", - RELOPT_KIND_TABLESPACE, - ShareUpdateExclusiveLock - }, - -1, 0.0, DBL_MAX - }, - { - { - "n_distinct", - "Sets the planner's estimate of the number of distinct values appearing in a column (excluding child relations).", - RELOPT_KIND_ATTRIBUTE, - ShareUpdateExclusiveLock - }, - 0, -1.0, DBL_MAX - }, - { - { - "n_distinct_inherited", - "Sets the planner's estimate of the number of distinct values appearing in a column (including child relations).", - RELOPT_KIND_ATTRIBUTE, - ShareUpdateExclusiveLock - }, - 0, -1.0, DBL_MAX - }, - { - { - "vacuum_cleanup_index_scale_factor", - "Deprecated B-Tree parameter.", - RELOPT_KIND_BTREE, - ShareUpdateExclusiveLock - }, - -1, 0.0, 1e10 - }, - /* list terminator */ - {{NULL}} -}; - /* values from StdRdOptIndexCleanup */ -static relopt_enum_elt_def StdRdOptIndexCleanupValues[] = +static opt_enum_elt_def StdRdOptIndexCleanupValues[] = { {"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO}, {"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON}, @@ -496,17 +104,8 @@ static relopt_enum_elt_def StdRdOptIndexCleanupValues[] = {(const char *) NULL} /* list terminator */ }; -/* values from GistOptBufferingMode */ -static relopt_enum_elt_def gistBufferingOptValues[] = -{ - {"auto", GIST_OPTION_BUFFERING_AUTO}, - {"on", GIST_OPTION_BUFFERING_ON}, - {"off", GIST_OPTION_BUFFERING_OFF}, - {(const char *) NULL} /* list terminator */ -}; - /* values from ViewOptCheckOption */ -static relopt_enum_elt_def viewCheckOptValues[] = +static opt_enum_elt_def viewCheckOptValues[] = { /* no value for NOT_SET */ {"local", VIEW_OPTION_CHECK_OPTION_LOCAL}, @@ -514,225 +113,9 @@ static relopt_enum_elt_def viewCheckOptValues[] = {(const char *) NULL} /* list terminator */ }; -static relopt_enum enumRelOpts[] = -{ - { - { - "vacuum_index_cleanup", - "Controls index vacuuming and index cleanup", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - StdRdOptIndexCleanupValues, - STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO, - gettext_noop("Valid values are \"on\", \"off\", and \"auto\".") - }, - { - { - "buffering", - "Enables buffering build for this GiST index", - RELOPT_KIND_GIST, - AccessExclusiveLock - }, - gistBufferingOptValues, - GIST_OPTION_BUFFERING_AUTO, - gettext_noop("Valid values are \"on\", \"off\", and \"auto\".") - }, - { - { - "check_option", - "View has WITH CHECK OPTION defined (local or cascaded).", - RELOPT_KIND_VIEW, - AccessExclusiveLock - }, - viewCheckOptValues, - VIEW_OPTION_CHECK_OPTION_NOT_SET, - gettext_noop("Valid values are \"local\" and \"cascaded\".") - }, - /* list terminator */ - {{NULL}} -}; -static relopt_string stringRelOpts[] = -{ - /* list terminator */ - {{NULL}} -}; - -static relopt_gen **relOpts = NULL; -static bits32 last_assigned_kind = RELOPT_KIND_LAST_DEFAULT; - -static int num_custom_options = 0; -static relopt_gen **custom_options = NULL; -static bool need_initialization = true; - -static void initialize_reloptions(void); -static void parse_one_reloption(relopt_value *option, char *text_str, - int text_len, bool validate); - -/* - * Get the length of a string reloption (either default or the user-defined - * value). This is used for allocation purposes when building a set of - * relation options. - */ -#define GET_STRING_RELOPTION_LEN(option) \ - ((option).isset ? strlen((option).values.string_val) : \ - ((relopt_string *) (option).gen)->default_len) - -/* - * initialize_reloptions - * initialization routine, must be called before parsing - * - * Initialize the relOpts array and fill each variable's type and name length. - */ -static void -initialize_reloptions(void) -{ - int i; - int j; - - j = 0; - for (i = 0; boolRelOpts[i].gen.name; i++) - { - Assert(DoLockModesConflict(boolRelOpts[i].gen.lockmode, - boolRelOpts[i].gen.lockmode)); - j++; - } - for (i = 0; intRelOpts[i].gen.name; i++) - { - Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, - intRelOpts[i].gen.lockmode)); - j++; - } - for (i = 0; realRelOpts[i].gen.name; i++) - { - Assert(DoLockModesConflict(realRelOpts[i].gen.lockmode, - realRelOpts[i].gen.lockmode)); - j++; - } - for (i = 0; enumRelOpts[i].gen.name; i++) - { - Assert(DoLockModesConflict(enumRelOpts[i].gen.lockmode, - enumRelOpts[i].gen.lockmode)); - j++; - } - for (i = 0; stringRelOpts[i].gen.name; i++) - { - Assert(DoLockModesConflict(stringRelOpts[i].gen.lockmode, - stringRelOpts[i].gen.lockmode)); - j++; - } - j += num_custom_options; - - if (relOpts) - pfree(relOpts); - relOpts = MemoryContextAlloc(TopMemoryContext, - (j + 1) * sizeof(relopt_gen *)); - - j = 0; - for (i = 0; boolRelOpts[i].gen.name; i++) - { - relOpts[j] = &boolRelOpts[i].gen; - relOpts[j]->type = RELOPT_TYPE_BOOL; - relOpts[j]->namelen = strlen(relOpts[j]->name); - j++; - } - - for (i = 0; intRelOpts[i].gen.name; i++) - { - relOpts[j] = &intRelOpts[i].gen; - relOpts[j]->type = RELOPT_TYPE_INT; - relOpts[j]->namelen = strlen(relOpts[j]->name); - j++; - } - - for (i = 0; realRelOpts[i].gen.name; i++) - { - relOpts[j] = &realRelOpts[i].gen; - relOpts[j]->type = RELOPT_TYPE_REAL; - relOpts[j]->namelen = strlen(relOpts[j]->name); - j++; - } - - for (i = 0; enumRelOpts[i].gen.name; i++) - { - relOpts[j] = &enumRelOpts[i].gen; - relOpts[j]->type = RELOPT_TYPE_ENUM; - relOpts[j]->namelen = strlen(relOpts[j]->name); - j++; - } - - for (i = 0; stringRelOpts[i].gen.name; i++) - { - relOpts[j] = &stringRelOpts[i].gen; - relOpts[j]->type = RELOPT_TYPE_STRING; - relOpts[j]->namelen = strlen(relOpts[j]->name); - j++; - } - - for (i = 0; i < num_custom_options; i++) - { - relOpts[j] = custom_options[i]; - j++; - } - - /* add a list terminator */ - relOpts[j] = NULL; - - /* flag the work is complete */ - need_initialization = false; -} - -/* - * add_reloption_kind - * Create a new relopt_kind value, to be used in custom reloptions by - * user-defined AMs. - */ -relopt_kind -add_reloption_kind(void) -{ - /* don't hand out the last bit so that the enum's behavior is portable */ - if (last_assigned_kind >= RELOPT_KIND_MAX) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("user-defined relation parameter types limit exceeded"))); - last_assigned_kind <<= 1; - return (relopt_kind) last_assigned_kind; -} - -/* - * add_reloption - * Add an already-created custom reloption to the list, and recompute the - * main parser table. - */ -static void -add_reloption(relopt_gen *newoption) -{ - static int max_custom_options = 0; - - if (num_custom_options >= max_custom_options) - { - MemoryContext oldcxt; - - oldcxt = MemoryContextSwitchTo(TopMemoryContext); - - if (max_custom_options == 0) - { - max_custom_options = 8; - custom_options = palloc(max_custom_options * sizeof(relopt_gen *)); - } - else - { - max_custom_options *= 2; - custom_options = repalloc(custom_options, - max_custom_options * sizeof(relopt_gen *)); - } - MemoryContextSwitchTo(oldcxt); - } - custom_options[num_custom_options++] = newoption; - - need_initialization = true; -} +options_spec_set *get_stdrd_relopt_spec_set(bool is_heap); +void oid_postvalidate(option_value *value); /* * init_local_reloptions @@ -742,9 +125,8 @@ add_reloption(relopt_gen *newoption) void init_local_reloptions(local_relopts *relopts, Size relopt_struct_size) { - relopts->options = NIL; relopts->validators = NIL; - relopts->relopt_struct_size = relopt_struct_size; + relopts->spec_set = allocateOptionsSpecSet(NULL, relopt_struct_size, true, 0); } /* @@ -758,112 +140,6 @@ register_reloptions_validator(local_relopts *relopts, relopts_validator validato relopts->validators = lappend(relopts->validators, validator); } -/* - * add_local_reloption - * Add an already-created custom reloption to the local list. - */ -static void -add_local_reloption(local_relopts *relopts, relopt_gen *newoption, int offset) -{ - local_relopt *opt = palloc(sizeof(*opt)); - - Assert(offset < relopts->relopt_struct_size); - - opt->option = newoption; - opt->offset = offset; - - relopts->options = lappend(relopts->options, opt); -} - -/* - * allocate_reloption - * Allocate a new reloption and initialize the type-agnostic fields - * (for types other than string) - */ -static relopt_gen * -allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, - LOCKMODE lockmode) -{ - MemoryContext oldcxt; - size_t size; - relopt_gen *newoption; - - if (kinds != RELOPT_KIND_LOCAL) - oldcxt = MemoryContextSwitchTo(TopMemoryContext); - else - oldcxt = NULL; - - switch (type) - { - case RELOPT_TYPE_BOOL: - size = sizeof(relopt_bool); - break; - case RELOPT_TYPE_INT: - size = sizeof(relopt_int); - break; - case RELOPT_TYPE_REAL: - size = sizeof(relopt_real); - break; - case RELOPT_TYPE_ENUM: - size = sizeof(relopt_enum); - break; - case RELOPT_TYPE_STRING: - size = sizeof(relopt_string); - break; - default: - elog(ERROR, "unsupported reloption type %d", type); - return NULL; /* keep compiler quiet */ - } - - newoption = palloc(size); - - newoption->name = pstrdup(name); - if (desc) - newoption->desc = pstrdup(desc); - else - newoption->desc = NULL; - newoption->kinds = kinds; - newoption->namelen = strlen(name); - newoption->type = type; - newoption->lockmode = lockmode; - - if (oldcxt != NULL) - MemoryContextSwitchTo(oldcxt); - - return newoption; -} - -/* - * init_bool_reloption - * Allocate and initialize a new boolean reloption - */ -static relopt_bool * -init_bool_reloption(bits32 kinds, const char *name, const char *desc, - bool default_val, LOCKMODE lockmode) -{ - relopt_bool *newoption; - - newoption = (relopt_bool *) allocate_reloption(kinds, RELOPT_TYPE_BOOL, - name, desc, lockmode); - newoption->default_val = default_val; - - return newoption; -} - -/* - * add_bool_reloption - * Add a new boolean reloption - */ -void -add_bool_reloption(bits32 kinds, const char *name, const char *desc, - bool default_val, LOCKMODE lockmode) -{ - relopt_bool *newoption = init_bool_reloption(kinds, name, desc, - default_val, lockmode); - - add_reloption((relopt_gen *) newoption); -} - /* * add_local_bool_reloption * Add a new boolean local reloption @@ -874,47 +150,8 @@ void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset) { - relopt_bool *newoption = init_bool_reloption(RELOPT_KIND_LOCAL, - name, desc, - default_val, 0); - - add_local_reloption(relopts, (relopt_gen *) newoption, offset); -} - - -/* - * init_real_reloption - * Allocate and initialize a new integer reloption - */ -static relopt_int * -init_int_reloption(bits32 kinds, const char *name, const char *desc, - int default_val, int min_val, int max_val, - LOCKMODE lockmode) -{ - relopt_int *newoption; - - newoption = (relopt_int *) allocate_reloption(kinds, RELOPT_TYPE_INT, - name, desc, lockmode); - newoption->default_val = default_val; - newoption->min = min_val; - newoption->max = max_val; - - return newoption; -} - -/* - * add_int_reloption - * Add a new integer reloption - */ -void -add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, - int min_val, int max_val, LOCKMODE lockmode) -{ - relopt_int *newoption = init_int_reloption(kinds, name, desc, - default_val, min_val, - max_val, lockmode); - - add_reloption((relopt_gen *) newoption); + optionsSpecSetAddBool(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val); } /* @@ -928,47 +165,8 @@ add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset) { - relopt_int *newoption = init_int_reloption(RELOPT_KIND_LOCAL, - name, desc, default_val, - min_val, max_val, 0); - - add_local_reloption(relopts, (relopt_gen *) newoption, offset); -} - -/* - * init_real_reloption - * Allocate and initialize a new real reloption - */ -static relopt_real * -init_real_reloption(bits32 kinds, const char *name, const char *desc, - double default_val, double min_val, double max_val, - LOCKMODE lockmode) -{ - relopt_real *newoption; - - newoption = (relopt_real *) allocate_reloption(kinds, RELOPT_TYPE_REAL, - name, desc, lockmode); - newoption->default_val = default_val; - newoption->min = min_val; - newoption->max = max_val; - - return newoption; -} - -/* - * add_real_reloption - * Add a new float reloption - */ -void -add_real_reloption(bits32 kinds, const char *name, const char *desc, - double default_val, double min_val, double max_val, - LOCKMODE lockmode) -{ - relopt_real *newoption = init_real_reloption(kinds, name, desc, - default_val, min_val, - max_val, lockmode); - - add_reloption((relopt_gen *) newoption); + optionsSpecSetAddInt(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); } /* @@ -978,142 +176,28 @@ add_real_reloption(bits32 kinds, const char *name, const char *desc, * 'offset' is offset of double-typed field. */ void -add_local_real_reloption(local_relopts *relopts, const char *name, - const char *desc, double default_val, - double min_val, double max_val, int offset) -{ - relopt_real *newoption = init_real_reloption(RELOPT_KIND_LOCAL, - name, desc, - default_val, min_val, - max_val, 0); - - add_local_reloption(relopts, (relopt_gen *) newoption, offset); -} - -/* - * init_enum_reloption - * Allocate and initialize a new enum reloption - */ -static relopt_enum * -init_enum_reloption(bits32 kinds, const char *name, const char *desc, - relopt_enum_elt_def *members, int default_val, - const char *detailmsg, LOCKMODE lockmode) -{ - relopt_enum *newoption; - - newoption = (relopt_enum *) allocate_reloption(kinds, RELOPT_TYPE_ENUM, - name, desc, lockmode); - newoption->members = members; - newoption->default_val = default_val; - newoption->detailmsg = detailmsg; - - return newoption; -} - - -/* - * add_enum_reloption - * Add a new enum reloption - * - * The members array must have a terminating NULL entry. - * - * The detailmsg is shown when unsupported values are passed, and has this - * form: "Valid values are \"foo\", \"bar\", and \"bar\"." - * - * The members array and detailmsg are not copied -- caller must ensure that - * they are valid throughout the life of the process. - */ -void -add_enum_reloption(bits32 kinds, const char *name, const char *desc, - relopt_enum_elt_def *members, int default_val, - const char *detailmsg, LOCKMODE lockmode) -{ - relopt_enum *newoption = init_enum_reloption(kinds, name, desc, - members, default_val, - detailmsg, lockmode); - - add_reloption((relopt_gen *) newoption); -} - -/* - * add_local_enum_reloption - * Add a new local enum reloption - * - * 'offset' is offset of int-typed field. - */ -void -add_local_enum_reloption(local_relopts *relopts, const char *name, - const char *desc, relopt_enum_elt_def *members, - int default_val, const char *detailmsg, int offset) -{ - relopt_enum *newoption = init_enum_reloption(RELOPT_KIND_LOCAL, - name, desc, - members, default_val, - detailmsg, 0); - - add_local_reloption(relopts, (relopt_gen *) newoption, offset); -} - -/* - * init_string_reloption - * Allocate and initialize a new string reloption - */ -static relopt_string * -init_string_reloption(bits32 kinds, const char *name, const char *desc, - const char *default_val, - validate_string_relopt validator, - fill_string_relopt filler, - LOCKMODE lockmode) -{ - relopt_string *newoption; - - /* make sure the validator/default combination is sane */ - if (validator) - (validator) (default_val); - - newoption = (relopt_string *) allocate_reloption(kinds, RELOPT_TYPE_STRING, - name, desc, lockmode); - newoption->validate_cb = validator; - newoption->fill_cb = filler; - if (default_val) - { - if (kinds == RELOPT_KIND_LOCAL) - newoption->default_val = strdup(default_val); - else - newoption->default_val = MemoryContextStrdup(TopMemoryContext, default_val); - newoption->default_len = strlen(default_val); - newoption->default_isnull = false; - } - else - { - newoption->default_val = ""; - newoption->default_len = 0; - newoption->default_isnull = true; - } +add_local_real_reloption(local_relopts *relopts, const char *name, + const char *desc, double default_val, + double min_val, double max_val, int offset) +{ + optionsSpecSetAddReal(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); - return newoption; } /* - * add_string_reloption - * Add a new string reloption + * add_local_enum_reloption + * Add a new local enum reloption * - * "validator" is an optional function pointer that can be used to test the - * validity of the values. It must elog(ERROR) when the argument string is - * not acceptable for the variable. Note that the default value must pass - * the validation. + * 'offset' is offset of int-typed field. */ void -add_string_reloption(bits32 kinds, const char *name, const char *desc, - const char *default_val, validate_string_relopt validator, - LOCKMODE lockmode) +add_local_enum_reloption(local_relopts *relopts, const char *name, + const char *desc, opt_enum_elt_def *members, + int default_val, const char *detailmsg, int offset) { - relopt_string *newoption = init_string_reloption(kinds, name, desc, - default_val, - validator, NULL, - lockmode); - - add_reloption((relopt_gen *) newoption); + optionsSpecSetAddEnum(relopts->spec_set, name, desc, NoLock, offset, NULL, + members, default_val, detailmsg); } /* @@ -1129,247 +213,8 @@ add_local_string_reloption(local_relopts *relopts, const char *name, validate_string_relopt validator, fill_string_relopt filler, int offset) { - relopt_string *newoption = init_string_reloption(RELOPT_KIND_LOCAL, - name, desc, - default_val, - validator, filler, - 0); - - add_local_reloption(relopts, (relopt_gen *) newoption, offset); -} - -/* - * Transform a relation options list (list of DefElem) into the text array - * format that is kept in pg_class.reloptions, including only those options - * that are in the passed namespace. The output values do not include the - * namespace. - * - * This is used for three cases: CREATE TABLE/INDEX, ALTER TABLE SET, and - * ALTER TABLE RESET. In the ALTER cases, oldOptions is the existing - * reloptions value (possibly NULL), and we replace or remove entries - * as needed. - * - * If acceptOidsOff is true, then we allow oids = false, but throw error when - * on. This is solely needed for backwards compatibility. - * - * Note that this is not responsible for determining whether the options - * are valid, but it does check that namespaces for all the options given are - * listed in validnsps. The NULL namespace is always valid and need not be - * explicitly listed. Passing a NULL pointer means that only the NULL - * namespace is valid. - * - * Both oldOptions and the result are text arrays (or NULL for "default"), - * but we declare them as Datums to avoid including array.h in reloptions.h. - */ -Datum -transformRelOptions(Datum oldOptions, List *defList, const char *namspace, - const char *const validnsps[], bool acceptOidsOff, bool isReset) -{ - Datum result; - ArrayBuildState *astate; - ListCell *cell; - - /* no change if empty list */ - if (defList == NIL) - return oldOptions; - - /* We build new array using accumArrayResult */ - astate = NULL; - - /* Copy any oldOptions that aren't to be replaced */ - if (PointerIsValid(DatumGetPointer(oldOptions))) - { - ArrayType *array = DatumGetArrayTypeP(oldOptions); - Datum *oldoptions; - int noldoptions; - int i; - - deconstruct_array_builtin(array, TEXTOID, &oldoptions, NULL, &noldoptions); - - for (i = 0; i < noldoptions; i++) - { - char *text_str = VARDATA(oldoptions[i]); - int text_len = VARSIZE(oldoptions[i]) - VARHDRSZ; - - /* Search for a match in defList */ - foreach(cell, defList) - { - DefElem *def = (DefElem *) lfirst(cell); - int kw_len; - - /* ignore if not in the same namespace */ - if (namspace == NULL) - { - if (def->defnamespace != NULL) - continue; - } - else if (def->defnamespace == NULL) - continue; - else if (strcmp(def->defnamespace, namspace) != 0) - continue; - - kw_len = strlen(def->defname); - if (text_len > kw_len && text_str[kw_len] == '=' && - strncmp(text_str, def->defname, kw_len) == 0) - break; - } - if (!cell) - { - /* No match, so keep old option */ - astate = accumArrayResult(astate, oldoptions[i], - false, TEXTOID, - CurrentMemoryContext); - } - } - } - - /* - * If CREATE/SET, add new options to array; if RESET, just check that the - * user didn't say RESET (option=val). (Must do this because the grammar - * doesn't enforce it.) - */ - foreach(cell, defList) - { - DefElem *def = (DefElem *) lfirst(cell); - - if (isReset) - { - if (def->arg != NULL) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("RESET must not include values for parameters"))); - } - else - { - text *t; - const char *value; - Size len; - - /* - * Error out if the namespace is not valid. A NULL namespace is - * always valid. - */ - if (def->defnamespace != NULL) - { - bool valid = false; - int i; - - if (validnsps) - { - for (i = 0; validnsps[i]; i++) - { - if (strcmp(def->defnamespace, validnsps[i]) == 0) - { - valid = true; - break; - } - } - } - - if (!valid) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized parameter namespace \"%s\"", - def->defnamespace))); - } - - /* ignore if not in the same namespace */ - if (namspace == NULL) - { - if (def->defnamespace != NULL) - continue; - } - else if (def->defnamespace == NULL) - continue; - else if (strcmp(def->defnamespace, namspace) != 0) - continue; - - /* - * Flatten the DefElem into a text string like "name=arg". If we - * have just "name", assume "name=true" is meant. Note: the - * namespace is not output. - */ - if (def->arg != NULL) - value = defGetString(def); - else - value = "true"; - - /* - * This is not a great place for this test, but there's no other - * convenient place to filter the option out. As WITH (oids = - * false) will be removed someday, this seems like an acceptable - * amount of ugly. - */ - if (acceptOidsOff && def->defnamespace == NULL && - strcmp(def->defname, "oids") == 0) - { - if (defGetBoolean(def)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("tables declared WITH OIDS are not supported"))); - /* skip over option, reloptions machinery doesn't know it */ - continue; - } - - len = VARHDRSZ + strlen(def->defname) + 1 + strlen(value); - /* +1 leaves room for sprintf's trailing null */ - t = (text *) palloc(len + 1); - SET_VARSIZE(t, len); - sprintf(VARDATA(t), "%s=%s", def->defname, value); - - astate = accumArrayResult(astate, PointerGetDatum(t), - false, TEXTOID, - CurrentMemoryContext); - } - } - - if (astate) - result = makeArrayResult(astate, CurrentMemoryContext); - else - result = (Datum) 0; - - return result; -} - - -/* - * Convert the text-array format of reloptions into a List of DefElem. - * This is the inverse of transformRelOptions(). - */ -List * -untransformRelOptions(Datum options) -{ - List *result = NIL; - ArrayType *array; - Datum *optiondatums; - int noptions; - int i; - - /* Nothing to do if no options */ - if (!PointerIsValid(DatumGetPointer(options))) - return result; - - array = DatumGetArrayTypeP(options); - - deconstruct_array_builtin(array, TEXTOID, &optiondatums, NULL, &noptions); - - for (i = 0; i < noptions; i++) - { - char *s; - char *p; - Node *val = NULL; - - s = TextDatumGetCString(optiondatums[i]); - p = strchr(s, '='); - if (p) - { - *p++ = '\0'; - val = (Node *) makeString(p); - } - result = lappend(result, makeDefElem(s, val, -1)); - } - - return result; + optionsSpecSetAddString(relopts->spec_set, name, desc, NoLock, offset, + NULL, default_val, validator, filler); } /* @@ -1386,12 +231,13 @@ untransformRelOptions(Datum options) */ bytea * extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, - amoptions_function amoptions) + amreloptspecset_function amoptionsspecsetfn) { bytea *options; bool isnull; Datum datum; Form_pg_class classForm; + options_spec_set *spec_set; datum = fastgetattr(tuple, Anum_pg_class_reloptions, @@ -1405,546 +251,340 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, /* Parse into appropriate format; don't error out here */ switch (classForm->relkind) { - case RELKIND_RELATION: case RELKIND_TOASTVALUE: + spec_set = get_toast_relopt_spec_set(); + break; + case RELKIND_RELATION: case RELKIND_MATVIEW: - options = heap_reloptions(classForm->relkind, datum, false); + spec_set = get_heap_relopt_spec_set(); break; case RELKIND_PARTITIONED_TABLE: - options = partitioned_table_reloptions(datum, false); + spec_set = get_partitioned_relopt_spec_set(); break; case RELKIND_VIEW: - options = view_reloptions(datum, false); + spec_set = get_view_relopt_spec_set(); break; case RELKIND_INDEX: case RELKIND_PARTITIONED_INDEX: - options = index_reloptions(amoptions, datum, false); + if (amoptionsspecsetfn) + spec_set = amoptionsspecsetfn(); + else + spec_set = NULL; break; case RELKIND_FOREIGN_TABLE: - options = NULL; + spec_set = NULL; break; default: Assert(false); /* can't get here */ - options = NULL; /* keep compiler quiet */ + spec_set = NULL; /* keep compiler quiet */ break; } - + if (spec_set) + options = optionsTextArrayToBytea(spec_set, datum, 0); + else + options = NULL; return options; } -static void -parseRelOptionsInternal(Datum options, bool validate, - relopt_value *reloptions, int numoptions) +void +oid_postvalidate(option_value *value) { - ArrayType *array = DatumGetArrayTypeP(options); - Datum *optiondatums; - int noptions; - int i; - - deconstruct_array_builtin(array, TEXTOID, &optiondatums, NULL, &noptions); - - for (i = 0; i < noptions; i++) - { - char *text_str = VARDATA(optiondatums[i]); - int text_len = VARSIZE(optiondatums[i]) - VARHDRSZ; - int j; - - /* Search for a match in reloptions */ - for (j = 0; j < numoptions; j++) - { - int kw_len = reloptions[j].gen->namelen; - - if (text_len > kw_len && text_str[kw_len] == '=' && - strncmp(text_str, reloptions[j].gen->name, kw_len) == 0) - { - parse_one_reloption(&reloptions[j], text_str, text_len, - validate); - break; - } - } - - if (j >= numoptions && validate) - { - char *s; - char *p; - - s = TextDatumGetCString(optiondatums[i]); - p = strchr(s, '='); - if (p) - *p = '\0'; - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized parameter \"%s\"", s))); - } - } - - /* It's worth avoiding memory leaks in this function */ - pfree(optiondatums); - - if (((void *) array) != DatumGetPointer(options)) - pfree(array); + if (value->values.bool_val) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("tables declared WITH OIDS are not supported"))); } /* - * Interpret reloptions that are given in text-array format. + * Relation options and Lock levels: + * + * The default choice for any new option should be AccessExclusiveLock. + * In some cases the lock level can be reduced from there, but the lock + * level chosen should always conflict with itself to ensure that multiple + * changes aren't lost when we attempt concurrent changes. + * The choice of lock level depends completely upon how that parameter + * is used within the server, not upon how and when you'd like to change it. + * Safety first. Existing choices are documented here, and elsewhere in + * backend code where the parameters are used. + * + * In general, anything that affects the results obtained from a SELECT must be + * protected by AccessExclusiveLock. * - * options is a reloption text array as constructed by transformRelOptions. - * kind specifies the family of options to be processed. + * Autovacuum related parameters can be set at ShareUpdateExclusiveLock + * since they are only used by the AV procs and don't change anything + * currently executing. * - * The return value is a relopt_value * array on which the options actually - * set in the options array are marked with isset=true. The length of this - * array is returned in *numrelopts. Options not set are also present in the - * array; this is so that the caller can easily locate the default values. + * Fillfactor can be set because it applies only to subsequent changes made to + * data blocks, as documented in heapio.c * - * If there are no options of the given kind, numrelopts is set to 0 and NULL - * is returned (unless options are illegally supplied despite none being - * defined, in which case an error occurs). + * n_distinct options can be set at ShareUpdateExclusiveLock because they + * are only used during ANALYZE, which uses a ShareUpdateExclusiveLock, + * so the ANALYZE will not be affected by in-flight changes. Changing those + * values has no affect until the next ANALYZE, so no need for stronger lock. * - * Note: values of type int, bool and real are allocated as part of the - * returned array. Values of type string are allocated separately and must - * be freed by the caller. - */ -static relopt_value * -parseRelOptions(Datum options, bool validate, relopt_kind kind, - int *numrelopts) -{ - relopt_value *reloptions = NULL; - int numoptions = 0; - int i; - int j; - - if (need_initialization) - initialize_reloptions(); - - /* Build a list of expected options, based on kind */ - - for (i = 0; relOpts[i]; i++) - if (relOpts[i]->kinds & kind) - numoptions++; - - if (numoptions > 0) - { - reloptions = palloc(numoptions * sizeof(relopt_value)); - - for (i = 0, j = 0; relOpts[i]; i++) - { - if (relOpts[i]->kinds & kind) - { - reloptions[j].gen = relOpts[i]; - reloptions[j].isset = false; - j++; - } - } + * Planner-related parameters can be set with ShareUpdateExclusiveLock because + * they only affect planning and not the correctness of the execution. Plans + * cannot be changed in mid-flight, so changes here could not easily result in + * new improved plans in any case. So we allow existing queries to continue + * and existing plans to survive, a small price to pay for allowing better + * plans to be introduced concurrently without interfering with users. + * + * Setting parallel_workers is safe, since it acts the same as + * max_parallel_workers_per_gather which is a USERSET parameter that doesn't + * affect existing plans or queries. + */ + + +options_spec_set * +get_stdrd_relopt_spec_set(bool is_heap) +{ + options_spec_set *stdrd_relopt_spec_set = allocateOptionsSpecSet( + is_heap ? NULL : "toast", sizeof(StdRdOptions), false, 0); + + if (is_heap) + optionsSpecSetAddInt(stdrd_relopt_spec_set, "fillfactor", + "Packs table pages only to this percentag", + ShareUpdateExclusiveLock, /* since it applies only + * to later inserts */ + offsetof(StdRdOptions, fillfactor), NULL, + HEAP_DEFAULT_FILLFACTOR, HEAP_MIN_FILLFACTOR, 100); + + optionsSpecSetAddBool(stdrd_relopt_spec_set, "autovacuum_enabled", + "Enables autovacuum in this relation", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, enabled), + NULL, true); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_vacuum_threshold", + "Minimum number of tuple updates or deletes prior to vacuum", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, vacuum_threshold), + NULL, -1, 0, INT_MAX); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_vacuum_max_threshold", + "Maximum number of tuple updates or deletes prior to vacuum", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, vacuum_max_threshold), + NULL, -2, -1, INT_MAX); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_vacuum_insert_threshold", + "Minimum number of tuple inserts prior to vacuum, or -1 to disable insert vacuums", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, vacuum_ins_threshold), + NULL, -2, -1, INT_MAX); + + if (is_heap) + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_analyze_threshold", + "Minimum number of tuple updates or deletes prior to vacuum", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, analyze_threshold), + NULL, -1, 0, INT_MAX); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_vacuum_cost_limit", + "Vacuum cost amount available before napping, for autovacuum", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, vacuum_cost_limit), + NULL, -1, 0, 10000); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_freeze_min_age", + "Minimum age at which VACUUM should freeze a table row, for autovacuum", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, freeze_min_age), + NULL, -1, 0, 1000000000); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_freeze_max_age", + "Age at which to autovacuum a table to prevent transaction ID wraparound", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, freeze_max_age), + NULL, -1, 100000, 2000000000); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_freeze_table_age", + "Age at which VACUUM should perform a full table sweep to freeze row versions", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, freeze_table_age), + NULL, -1, 0, 2000000000); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_multixact_freeze_min_age", + "Minimum multixact age at which VACUUM should freeze a row multixact's, for autovacuum", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, multixact_freeze_min_age), + NULL, -1, 0, 1000000000); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_multixact_freeze_max_age", + "Multixact age at which to autovacuum a table to prevent multixact wraparound", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, multixact_freeze_max_age), + NULL, -1, 10000, 2000000000); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_multixact_freeze_table_age", + "Age of multixact at which VACUUM should perform a full table sweep to freeze row versions", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, multixact_freeze_table_age), + NULL, -1, 0, 2000000000); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "log_autovacuum_min_duration", + "Sets the minimum execution time above which autovacuum actions will be logged", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, log_min_duration), + NULL, -1, -1, INT_MAX); + + optionsSpecSetAddReal(stdrd_relopt_spec_set, "autovacuum_vacuum_cost_delay", + "Vacuum cost delay in milliseconds, for autovacuum", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, vacuum_cost_delay), + NULL, -1, 0.0, 100.0); + + optionsSpecSetAddReal(stdrd_relopt_spec_set, "autovacuum_vacuum_scale_factor", + "Number of tuple updates or deletes prior to vacuum as a fraction of reltuples", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, vacuum_scale_factor), + NULL, -1, 0.0, 100.0); + + optionsSpecSetAddReal(stdrd_relopt_spec_set, "autovacuum_vacuum_insert_scale_factor", + "Number of tuple inserts prior to vacuum as a fraction of reltuples", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, vacuum_ins_scale_factor), + NULL, -1, 0.0, 100.0); + if (is_heap) + { + optionsSpecSetAddReal(stdrd_relopt_spec_set, "autovacuum_analyze_scale_factor", + "Number of tuple inserts, updates or deletes prior to analyze as a fraction of reltuples", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, analyze_scale_factor), + NULL, -1, 0.0, 100.0); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "toast_tuple_target", + "Sets the target tuple length at which external columns will be toasted", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, toast_tuple_target), + NULL, TOAST_TUPLE_TARGET, 128, + TOAST_TUPLE_TARGET_MAIN); + + optionsSpecSetAddBool(stdrd_relopt_spec_set, "user_catalog_table", + "Declare a table as an additional catalog table, e.g. for the purpose of logical replication", + AccessExclusiveLock, + offsetof(StdRdOptions, user_catalog_table), + NULL, false); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "parallel_workers", + "Number of parallel processes that can be used per executor node for this relation.", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, parallel_workers), + NULL, -1, 0, 1024); } - /* Done if no options */ - if (PointerIsValid(DatumGetPointer(options))) - parseRelOptionsInternal(options, validate, reloptions, numoptions); - - *numrelopts = numoptions; - return reloptions; -} - -/* Parse local unregistered options. */ -static relopt_value * -parseLocalRelOptions(local_relopts *relopts, Datum options, bool validate) -{ - int nopts = list_length(relopts->options); - relopt_value *values = palloc(sizeof(*values) * nopts); - ListCell *lc; - int i = 0; + optionsSpecSetAddEnum(stdrd_relopt_spec_set, "vacuum_index_cleanup", + "Controls index vacuuming and index cleanup", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, vacuum_index_cleanup), + NULL, StdRdOptIndexCleanupValues, + STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO, + gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")); - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); - - values[i].gen = opt->option; - values[i].isset = false; - - i++; - } + optionsSpecSetAddBool(stdrd_relopt_spec_set, "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, vacuum_truncate), + NULL, true); - if (options != (Datum) 0) - parseRelOptionsInternal(options, validate, values, nopts); + if (is_heap) + optionsSpecSetAddBool(stdrd_relopt_spec_set, "oids", + "Backward compatibility option. Will do nothing when false, will throw error when true", + NoLock, + -1, /* Do not actually store it's value */ + &oid_postvalidate, false); - return values; + return stdrd_relopt_spec_set; } -/* - * Subroutine for parseRelOptions, to parse and validate a single option's - * value - */ -static void -parse_one_reloption(relopt_value *option, char *text_str, int text_len, - bool validate) -{ - char *value; - int value_len; - bool parsed; - bool nofree = false; - if (option->isset && validate) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("parameter \"%s\" specified more than once", - option->gen->name))); - - value_len = text_len - option->gen->namelen - 1; - value = (char *) palloc(value_len + 1); - memcpy(value, text_str + option->gen->namelen + 1, value_len); - value[value_len] = '\0'; - - switch (option->gen->type) - { - case RELOPT_TYPE_BOOL: - { - parsed = parse_bool(value, &option->values.bool_val); - if (validate && !parsed) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid value for boolean option \"%s\": %s", - option->gen->name, value))); - } - break; - case RELOPT_TYPE_INT: - { - relopt_int *optint = (relopt_int *) option->gen; - - parsed = parse_int(value, &option->values.int_val, 0, NULL); - if (validate && !parsed) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid value for integer option \"%s\": %s", - option->gen->name, value))); - if (validate && (option->values.int_val < optint->min || - option->values.int_val > optint->max)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("value %s out of bounds for option \"%s\"", - value, option->gen->name), - errdetail("Valid values are between \"%d\" and \"%d\".", - optint->min, optint->max))); - } - break; - case RELOPT_TYPE_REAL: - { - relopt_real *optreal = (relopt_real *) option->gen; - - parsed = parse_real(value, &option->values.real_val, 0, NULL); - if (validate && !parsed) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid value for floating point option \"%s\": %s", - option->gen->name, value))); - if (validate && (option->values.real_val < optreal->min || - option->values.real_val > optreal->max)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("value %s out of bounds for option \"%s\"", - value, option->gen->name), - errdetail("Valid values are between \"%f\" and \"%f\".", - optreal->min, optreal->max))); - } - break; - case RELOPT_TYPE_ENUM: - { - relopt_enum *optenum = (relopt_enum *) option->gen; - relopt_enum_elt_def *elt; - - parsed = false; - for (elt = optenum->members; elt->string_val; elt++) - { - if (pg_strcasecmp(value, elt->string_val) == 0) - { - option->values.enum_val = elt->symbol_val; - parsed = true; - break; - } - } - if (validate && !parsed) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid value for enum option \"%s\": %s", - option->gen->name, value), - optenum->detailmsg ? - errdetail_internal("%s", _(optenum->detailmsg)) : 0)); - - /* - * If value is not among the allowed string values, but we are - * not asked to validate, just use the default numeric value. - */ - if (!parsed) - option->values.enum_val = optenum->default_val; - } - break; - case RELOPT_TYPE_STRING: - { - relopt_string *optstring = (relopt_string *) option->gen; - - option->values.string_val = value; - nofree = true; - if (validate && optstring->validate_cb) - (optstring->validate_cb) (value); - parsed = true; - } - break; - default: - elog(ERROR, "unsupported reloption type %d", option->gen->type); - parsed = true; /* quiet compiler */ - break; - } +static options_spec_set *heap_relopt_spec_set = NULL; - if (parsed) - option->isset = true; - if (!nofree) - pfree(value); +options_spec_set * +get_heap_relopt_spec_set(void) +{ + if (heap_relopt_spec_set) + return heap_relopt_spec_set; + heap_relopt_spec_set = get_stdrd_relopt_spec_set(true); + return heap_relopt_spec_set; } /* - * Given the result from parseRelOptions, allocate a struct that's of the - * specified base size plus any extra space that's needed for string variables. - * - * "base" should be sizeof(struct) of the reloptions struct (StdRdOptions or - * equivalent). + * These toast options are can't be set via SQL, but we should set them + * to their defaults in binary representation, to make postgres work properly */ -static void * -allocateReloptStruct(Size base, relopt_value *options, int numoptions) +static void +toast_options_postprocess(void *data, bool validate) { - Size size = base; - int i; - - for (i = 0; i < numoptions; i++) + if (data) { - relopt_value *optval = &options[i]; + StdRdOptions *toast_options = (StdRdOptions *) data; - if (optval->gen->type == RELOPT_TYPE_STRING) - { - relopt_string *optstr = (relopt_string *) optval->gen; - - if (optstr->fill_cb) - { - const char *val = optval->isset ? optval->values.string_val : - optstr->default_isnull ? NULL : optstr->default_val; - - size += optstr->fill_cb(val, NULL); - } - else - size += GET_STRING_RELOPTION_LEN(*optval) + 1; - } + toast_options->fillfactor = 100; + toast_options->autovacuum.analyze_threshold = -1; + toast_options->autovacuum.analyze_scale_factor = -1; } - - return palloc0(size); } -/* - * Given the result of parseRelOptions and a parsing table, fill in the - * struct (previously allocated with allocateReloptStruct) with the parsed - * values. - * - * rdopts is the pointer to the allocated struct to be filled. - * basesize is the sizeof(struct) that was passed to allocateReloptStruct. - * options, of length numoptions, is parseRelOptions' output. - * elems, of length numelems, is the table describing the allowed options. - * When validate is true, it is expected that all options appear in elems. - */ -static void -fillRelOptions(void *rdopts, Size basesize, - relopt_value *options, int numoptions, - bool validate, - const relopt_parse_elt *elems, int numelems) +static options_spec_set *toast_relopt_spec_set = NULL; +options_spec_set * +get_toast_relopt_spec_set(void) { - int i; - int offset = basesize; + if (toast_relopt_spec_set) + return toast_relopt_spec_set; - for (i = 0; i < numoptions; i++) - { - int j; - bool found = false; + toast_relopt_spec_set = get_stdrd_relopt_spec_set(false); + toast_relopt_spec_set->postprocess_fun = toast_options_postprocess; - for (j = 0; j < numelems; j++) - { - if (strcmp(options[i].gen->name, elems[j].optname) == 0) - { - relopt_string *optstring; - char *itempos = ((char *) rdopts) + elems[j].offset; - char *string_val; - - switch (options[i].gen->type) - { - case RELOPT_TYPE_BOOL: - *(bool *) itempos = options[i].isset ? - options[i].values.bool_val : - ((relopt_bool *) options[i].gen)->default_val; - break; - case RELOPT_TYPE_INT: - *(int *) itempos = options[i].isset ? - options[i].values.int_val : - ((relopt_int *) options[i].gen)->default_val; - break; - case RELOPT_TYPE_REAL: - *(double *) itempos = options[i].isset ? - options[i].values.real_val : - ((relopt_real *) options[i].gen)->default_val; - break; - case RELOPT_TYPE_ENUM: - *(int *) itempos = options[i].isset ? - options[i].values.enum_val : - ((relopt_enum *) options[i].gen)->default_val; - break; - case RELOPT_TYPE_STRING: - optstring = (relopt_string *) options[i].gen; - if (options[i].isset) - string_val = options[i].values.string_val; - else if (!optstring->default_isnull) - string_val = optstring->default_val; - else - string_val = NULL; - - if (optstring->fill_cb) - { - Size size = - optstring->fill_cb(string_val, - (char *) rdopts + offset); - - if (size) - { - *(int *) itempos = offset; - offset += size; - } - else - *(int *) itempos = 0; - } - else if (string_val == NULL) - *(int *) itempos = 0; - else - { - strcpy((char *) rdopts + offset, string_val); - *(int *) itempos = offset; - offset += strlen(string_val) + 1; - } - break; - default: - elog(ERROR, "unsupported reloption type %d", - options[i].gen->type); - break; - } - found = true; - break; - } - } - if (validate && !found) - elog(ERROR, "reloption \"%s\" not found in parse table", - options[i].gen->name); - } - SET_VARSIZE(rdopts, offset); + return toast_relopt_spec_set; } /* - * Option parser for anything that uses StdRdOptions. + * Do not allow to set any option on partitioned table */ -bytea * -default_reloptions(Datum reloptions, bool validate, relopt_kind kind) +static void +partitioned_options_postprocess(void *data, bool validate) { - static const relopt_parse_elt tab[] = { - {"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)}, - {"autovacuum_enabled", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)}, - {"autovacuum_vacuum_threshold", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_threshold)}, - {"autovacuum_vacuum_max_threshold", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_max_threshold)}, - {"autovacuum_vacuum_insert_threshold", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_ins_threshold)}, - {"autovacuum_analyze_threshold", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_threshold)}, - {"autovacuum_vacuum_cost_limit", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_limit)}, - {"autovacuum_freeze_min_age", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_min_age)}, - {"autovacuum_freeze_max_age", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_max_age)}, - {"autovacuum_freeze_table_age", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_table_age)}, - {"autovacuum_multixact_freeze_min_age", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_min_age)}, - {"autovacuum_multixact_freeze_max_age", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_max_age)}, - {"autovacuum_multixact_freeze_table_age", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_table_age)}, - {"log_autovacuum_min_duration", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, log_min_duration)}, - {"toast_tuple_target", RELOPT_TYPE_INT, - offsetof(StdRdOptions, toast_tuple_target)}, - {"autovacuum_vacuum_cost_delay", RELOPT_TYPE_REAL, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_delay)}, - {"autovacuum_vacuum_scale_factor", RELOPT_TYPE_REAL, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_scale_factor)}, - {"autovacuum_vacuum_insert_scale_factor", RELOPT_TYPE_REAL, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_ins_scale_factor)}, - {"autovacuum_analyze_scale_factor", RELOPT_TYPE_REAL, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_scale_factor)}, - {"user_catalog_table", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, user_catalog_table)}, - {"parallel_workers", RELOPT_TYPE_INT, - offsetof(StdRdOptions, parallel_workers)}, - {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, - offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate)} - }; - - return (bytea *) build_reloptions(reloptions, validate, kind, - sizeof(StdRdOptions), - tab, lengthof(tab)); + if (data && validate) + ereport(ERROR, + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot specify storage parameters for a partitioned table"), + errhint("Specify storage parameters for its leaf partitions instead.")); } -/* - * build_reloptions - * - * Parses "reloptions" provided by the caller, returning them in a - * structure containing the parsed options. The parsing is done with - * the help of a parsing table describing the allowed options, defined - * by "relopt_elems" of length "num_relopt_elems". - * - * "validate" must be true if reloptions value is freshly built by - * transformRelOptions(), as opposed to being read from the catalog, in which - * case the values contained in it must already be valid. - * - * NULL is returned if the passed-in options did not match any of the options - * in the parsing table, unless validate is true in which case an error would - * be reported. - */ -void * -build_reloptions(Datum reloptions, bool validate, - relopt_kind kind, - Size relopt_struct_size, - const relopt_parse_elt *relopt_elems, - int num_relopt_elems) -{ - int numoptions; - relopt_value *options; - void *rdopts; - - /* parse options specific to given relation option kind */ - options = parseRelOptions(reloptions, validate, kind, &numoptions); - Assert(numoptions <= num_relopt_elems); - /* if none set, we're done */ - if (numoptions == 0) - { - Assert(options == NULL); - return NULL; - } +static options_spec_set *partitioned_relopt_spec_set = NULL; - /* allocate and fill the structure */ - rdopts = allocateReloptStruct(relopt_struct_size, options, numoptions); - fillRelOptions(rdopts, relopt_struct_size, options, numoptions, - validate, relopt_elems, num_relopt_elems); +options_spec_set * +get_partitioned_relopt_spec_set(void) +{ + if (partitioned_relopt_spec_set) + return partitioned_relopt_spec_set; + partitioned_relopt_spec_set = get_stdrd_relopt_spec_set(true); - pfree(options); + /* No options for now, so Spec Set is empty */ + partitioned_relopt_spec_set->postprocess_fun = + partitioned_options_postprocess; - return rdopts; + return partitioned_relopt_spec_set; } /* @@ -1955,158 +595,165 @@ build_reloptions(Datum reloptions, bool validate, void * build_local_reloptions(local_relopts *relopts, Datum options, bool validate) { - int noptions = list_length(relopts->options); - relopt_parse_elt *elems = palloc(sizeof(*elems) * noptions); - relopt_value *vals; void *opts; - int i = 0; ListCell *lc; + List *values; + + values = optionsTextArrayToRawValues(options); + values = optionsParseRawValues(values, relopts->spec_set, validate); + opts = optionsValuesToBytea(values, relopts->spec_set); + + /* + * Kind of ugly conversion here for backward compatibility. Would be + * removed while moving opclass options to options.c API + */ - foreach(lc, relopts->options) + if (validate && relopts->validators) { - local_relopt *opt = lfirst(lc); + int val_count = list_length(values); + int i; + option_value *val_array; - elems[i].optname = opt->option->name; - elems[i].opttype = opt->option->type; - elems[i].offset = opt->offset; + val_array = palloc(sizeof(option_value) * val_count); - i++; - } + i = 0; + foreach(lc, values) + { + option_value *val = lfirst(lc); - vals = parseLocalRelOptions(relopts, options, validate); - opts = allocateReloptStruct(relopts->relopt_struct_size, vals, noptions); - fillRelOptions(opts, relopts->relopt_struct_size, vals, noptions, validate, - elems, noptions); + memcpy(&(val_array[i]), val, sizeof(option_value)); + i++; + } - if (validate) foreach(lc, relopts->validators) - ((relopts_validator) lfirst(lc)) (opts, vals, noptions); + ((relopts_validator) lfirst(lc)) (opts, val_array, val_count); - if (elems) - pfree(elems); + pfree(val_array); + } return opts; } /* - * Option parser for partitioned tables - */ -bytea * -partitioned_table_reloptions(Datum reloptions, bool validate) -{ - if (validate && reloptions) - ereport(ERROR, - errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot specify storage parameters for a partitioned table"), - errhint("Specify storage parameters for its leaf partitions instead.")); - return NULL; -} - -/* - * Option parser for views + * get_view_relopt_spec_set + * Returns an options catalog for view relation. */ -bytea * -view_reloptions(Datum reloptions, bool validate) -{ - static const relopt_parse_elt tab[] = { - {"security_barrier", RELOPT_TYPE_BOOL, - offsetof(ViewOptions, security_barrier)}, - {"security_invoker", RELOPT_TYPE_BOOL, - offsetof(ViewOptions, security_invoker)}, - {"check_option", RELOPT_TYPE_ENUM, - offsetof(ViewOptions, check_option)} - }; - - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_VIEW, - sizeof(ViewOptions), - tab, lengthof(tab)); -} +static options_spec_set *view_relopt_spec_set = NULL; -/* - * Parse options for heaps, views and toast tables. - */ -bytea * -heap_reloptions(char relkind, Datum reloptions, bool validate) +options_spec_set * +get_view_relopt_spec_set(void) { - StdRdOptions *rdopts; + if (view_relopt_spec_set) + return view_relopt_spec_set; - switch (relkind) - { - case RELKIND_TOASTVALUE: - rdopts = (StdRdOptions *) - default_reloptions(reloptions, validate, RELOPT_KIND_TOAST); - if (rdopts != NULL) - { - /* adjust default-only parameters for TOAST relations */ - rdopts->fillfactor = 100; - rdopts->autovacuum.analyze_threshold = -1; - rdopts->autovacuum.analyze_scale_factor = -1; - } - return (bytea *) rdopts; - case RELKIND_RELATION: - case RELKIND_MATVIEW: - return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP); - default: - /* other relkinds are not supported */ - return NULL; - } -} + view_relopt_spec_set = allocateOptionsSpecSet(NULL, + sizeof(ViewOptions), false, 3); + optionsSpecSetAddBool(view_relopt_spec_set, "security_barrier", + "View acts as a row security barrier", + AccessExclusiveLock, + offsetof(ViewOptions, security_barrier), NULL, false); -/* - * Parse options for indexes. - * - * amoptions index AM's option parser function - * reloptions options as text[] datum - * validate error flag - */ -bytea * -index_reloptions(amoptions_function amoptions, Datum reloptions, bool validate) -{ - Assert(amoptions != NULL); + optionsSpecSetAddBool(view_relopt_spec_set, "security_invoker", + "Privileges on underlying relations are checked as the invoking user, not the view owner", + AccessExclusiveLock, + offsetof(ViewOptions, security_invoker), NULL, false); - /* Assume function is strict */ - if (!PointerIsValid(DatumGetPointer(reloptions))) - return NULL; + optionsSpecSetAddEnum(view_relopt_spec_set, "check_option", + "View has WITH CHECK OPTION defined (local or cascaded)", + AccessExclusiveLock, + offsetof(ViewOptions, check_option), NULL, + viewCheckOptValues, + VIEW_OPTION_CHECK_OPTION_NOT_SET, + gettext_noop("Valid values are \"local\" and \"cascaded\".")); - return amoptions(reloptions, validate); + return view_relopt_spec_set; } /* - * Option parser for attribute reloptions + * get_attribute_options_spec_set + * Returns an Options Spec Set for heap attributes */ -bytea * -attribute_reloptions(Datum reloptions, bool validate) +static options_spec_set *attribute_options_spec_set = NULL; + +options_spec_set * +get_attribute_options_spec_set(void) { - static const relopt_parse_elt tab[] = { - {"n_distinct", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct)}, - {"n_distinct_inherited", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct_inherited)} - }; - - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_ATTRIBUTE, - sizeof(AttributeOpts), - tab, lengthof(tab)); + if (attribute_options_spec_set) + return attribute_options_spec_set; + + attribute_options_spec_set = allocateOptionsSpecSet(NULL, + sizeof(AttributeOpts), false, 2); + + optionsSpecSetAddReal(attribute_options_spec_set, "n_distinct", + "Sets the planner's estimate of the number of distinct values appearing in a column (excluding child relations).", + ShareUpdateExclusiveLock, + offsetof(AttributeOpts, n_distinct), NULL, + 0, -1.0, DBL_MAX); + + optionsSpecSetAddReal(attribute_options_spec_set, + "n_distinct_inherited", + "Sets the planner's estimate of the number of distinct values appearing in a column (including child relations).", + ShareUpdateExclusiveLock, + offsetof(AttributeOpts, n_distinct_inherited), NULL, + 0, -1.0, DBL_MAX); + + return attribute_options_spec_set; } + /* - * Option parser for tablespace reloptions - */ -bytea * -tablespace_reloptions(Datum reloptions, bool validate) + * get_tablespace_options_spec_set + * Returns an Options Spec Set for tablespaces +*/ +static options_spec_set *tablespace_options_spec_set = NULL; + +options_spec_set * +get_tablespace_options_spec_set(void) { - static const relopt_parse_elt tab[] = { - {"random_page_cost", RELOPT_TYPE_REAL, offsetof(TableSpaceOpts, random_page_cost)}, - {"seq_page_cost", RELOPT_TYPE_REAL, offsetof(TableSpaceOpts, seq_page_cost)}, - {"effective_io_concurrency", RELOPT_TYPE_INT, offsetof(TableSpaceOpts, effective_io_concurrency)}, - {"maintenance_io_concurrency", RELOPT_TYPE_INT, offsetof(TableSpaceOpts, maintenance_io_concurrency)} - }; - - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_TABLESPACE, - sizeof(TableSpaceOpts), - tab, lengthof(tab)); + if (tablespace_options_spec_set) + return tablespace_options_spec_set; + + tablespace_options_spec_set = allocateOptionsSpecSet(NULL, + sizeof(TableSpaceOpts), false, 4); + optionsSpecSetAddReal(tablespace_options_spec_set, + "random_page_cost", + "Sets the planner's estimate of the cost of a nonsequentially fetched disk page", + ShareUpdateExclusiveLock, + offsetof(TableSpaceOpts, random_page_cost), + NULL, -1, 0.0, DBL_MAX); + + optionsSpecSetAddReal(tablespace_options_spec_set, "seq_page_cost", + "Sets the planner's estimate of the cost of a sequentially fetched disk page", + ShareUpdateExclusiveLock, + offsetof(TableSpaceOpts, seq_page_cost), + NULL, -1, 0.0, DBL_MAX); + + optionsSpecSetAddInt(tablespace_options_spec_set, "effective_io_concurrency", + "Number of simultaneous requests that can be handled efficiently by the disk subsystem", + ShareUpdateExclusiveLock, + offsetof(TableSpaceOpts, effective_io_concurrency), + NULL, +#ifdef USE_PREFETCH + -1, 0, MAX_IO_CONCURRENCY +#else + 0, 0, 0 +#endif + ); + + optionsSpecSetAddInt(tablespace_options_spec_set, + "maintenance_io_concurrency", + "Number of simultaneous requests that can be handled efficiently by the disk subsystem for maintenance work.", + ShareUpdateExclusiveLock, + offsetof(TableSpaceOpts, maintenance_io_concurrency), + NULL, +#ifdef USE_PREFETCH + -1, 0, MAX_IO_CONCURRENCY +#else + 0, 0, 0 +#endif + ); + return tablespace_options_spec_set; } /* @@ -2116,33 +763,55 @@ tablespace_reloptions(Datum reloptions, bool validate) * for a longer explanation of how this works. */ LOCKMODE -AlterTableGetRelOptionsLockLevel(List *defList) +AlterTableGetRelOptionsLockLevel(Relation rel, List *defList) { LOCKMODE lockmode = NoLock; ListCell *cell; + options_spec_set *spec_set = NULL; if (defList == NIL) return AccessExclusiveLock; - if (need_initialization) - initialize_reloptions(); + switch (rel->rd_rel->relkind) + { + case RELKIND_TOASTVALUE: + spec_set = get_toast_relopt_spec_set(); + break; + case RELKIND_RELATION: + case RELKIND_MATVIEW: + spec_set = get_heap_relopt_spec_set(); + break; + case RELKIND_INDEX: + spec_set = rel->rd_indam->amreloptspecset(); + break; + case RELKIND_VIEW: + spec_set = get_view_relopt_spec_set(); + break; + case RELKIND_PARTITIONED_TABLE: + spec_set = get_partitioned_relopt_spec_set(); + break; + default: + Assert(false); /* can't get here */ + break; + } + Assert(spec_set); /* No spec set - no reloption change. Should + * never get here */ foreach(cell, defList) { DefElem *def = (DefElem *) lfirst(cell); + int i; - for (i = 0; relOpts[i]; i++) + for (i = 0; i < spec_set->num; i++) { - if (strncmp(relOpts[i]->name, - def->defname, - relOpts[i]->namelen + 1) == 0) - { - if (lockmode < relOpts[i]->lockmode) - lockmode = relOpts[i]->lockmode; - } + option_spec_basic *gen = spec_set->definitions[i]; + + if (pg_strcasecmp(gen->name, + def->defname) == 0) + if (lockmode < gen->lockmode) + lockmode = gen->lockmode; } } - return lockmode; } diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c index 2500d16b7bc..1526bee6bde 100644 --- a/src/backend/access/gin/ginutil.c +++ b/src/backend/access/gin/ginutil.c @@ -16,7 +16,7 @@ #include "access/gin_private.h" #include "access/ginxlog.h" -#include "access/reloptions.h" +#include "access/options.h" #include "access/xloginsert.h" #include "catalog/pg_collation.h" #include "catalog/pg_type.h" @@ -27,6 +27,7 @@ #include "utils/index_selfuncs.h" #include "utils/rel.h" #include "utils/typcache.h" +#include "utils/guc.h" /* @@ -70,7 +71,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; amroutine->amgettreeheight = NULL; - amroutine->amoptions = ginoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = ginvalidate; @@ -85,6 +85,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gingetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -599,21 +600,6 @@ ginExtractEntries(GinState *ginstate, OffsetNumber attnum, return entries; } -bytea * -ginoptions(Datum reloptions, bool validate) -{ - static const relopt_parse_elt tab[] = { - {"fastupdate", RELOPT_TYPE_BOOL, offsetof(GinOptions, useFastUpdate)}, - {"gin_pending_list_limit", RELOPT_TYPE_INT, offsetof(GinOptions, - pendingListCleanupSize)} - }; - - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_GIN, - sizeof(GinOptions), - tab, lengthof(tab)); -} - /* * Fetch index's statistical data into *stats * @@ -700,3 +686,29 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build) END_CRIT_SECTION(); } + +static options_spec_set *gin_relopt_specset = NULL; + +options_spec_set * +gingetreloptspecset(void) +{ + if (gin_relopt_specset) + return gin_relopt_specset; + + gin_relopt_specset = allocateOptionsSpecSet(NULL, + sizeof(GinOptions), false, 2); + + optionsSpecSetAddBool(gin_relopt_specset, "fastupdate", + "Enables \"fast update\" feature for this GIN index", + AccessExclusiveLock, + offsetof(GinOptions, useFastUpdate), NULL, + GIN_DEFAULT_USE_FASTUPDATE); + + optionsSpecSetAddInt(gin_relopt_specset, "gin_pending_list_limit", + "Maximum size of the pending list for this GIN index, in kilobytes", + AccessExclusiveLock, + offsetof(GinOptions, pendingListCleanupSize), NULL, + -1, 64, MAX_KILOBYTES); + + return gin_relopt_specset; +} diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index 4d858b65e1e..b017ae6fcdd 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -92,7 +92,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; amroutine->amgettreeheight = NULL; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -109,6 +108,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amparallelrescan = NULL; amroutine->amtranslatestrategy = NULL; amroutine->amtranslatecmptype = gisttranslatecmptype; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index dbc4ac639a2..bb3eea8b1dd 100644 --- a/src/backend/access/gist/gistutil.c +++ b/src/backend/access/gist/gistutil.c @@ -17,7 +17,7 @@ #include "access/gist_private.h" #include "access/htup_details.h" -#include "access/reloptions.h" +#include "access/options.h" #include "common/pg_prng.h" #include "storage/indexfsm.h" #include "utils/float.h" @@ -908,20 +908,6 @@ gistPageRecyclable(Page page) return false; } -bytea * -gistoptions(Datum reloptions, bool validate) -{ - static const relopt_parse_elt tab[] = { - {"fillfactor", RELOPT_TYPE_INT, offsetof(GiSTOptions, fillfactor)}, - {"buffering", RELOPT_TYPE_ENUM, offsetof(GiSTOptions, buffering_mode)} - }; - - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_GIST, - sizeof(GiSTOptions), - tab, lengthof(tab)); -} - /* * gistproperty() -- Check boolean properties of indexes. * @@ -1109,3 +1095,39 @@ gisttranslatecmptype(CompareType cmptype, Oid opfamily, Oid opcintype) result = OidFunctionCall1Coll(funcid, InvalidOid, Int32GetDatum(cmptype)); return DatumGetUInt16(result); } + +/* values from GistOptBufferingMode */ +static opt_enum_elt_def gistBufferingOptValues[] = +{ + {"auto", GIST_OPTION_BUFFERING_AUTO}, + {"on", GIST_OPTION_BUFFERING_ON}, + {"off", GIST_OPTION_BUFFERING_OFF}, + {(const char *) NULL} /* list terminator */ +}; + +static options_spec_set *gist_relopt_specset = NULL; + +options_spec_set * +gistgetreloptspecset(void) +{ + if (gist_relopt_specset) + return gist_relopt_specset; + + gist_relopt_specset = allocateOptionsSpecSet(NULL, + sizeof(GiSTOptions), false, 2); + + optionsSpecSetAddInt(gist_relopt_specset, "fillfactor", + "Packs gist index pages only to this percentage", + NoLock, /* No ALTER, no lock */ + offsetof(GiSTOptions, fillfactor), NULL, + GIST_DEFAULT_FILLFACTOR, GIST_MIN_FILLFACTOR, 100); + + optionsSpecSetAddEnum(gist_relopt_specset, "buffering", + "Enables buffering build for this GiST index", + NoLock, /* No ALTER, no lock */ + offsetof(GiSTOptions, buffering_mode), NULL, + gistBufferingOptValues, + GIST_OPTION_BUFFERING_AUTO, + gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")); + return gist_relopt_specset; +} diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index 63b568e7f24..33239680056 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -91,7 +91,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; amroutine->amgettreeheight = NULL; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -108,6 +107,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amparallelrescan = NULL; amroutine->amtranslatestrategy = hashtranslatestrategy; amroutine->amtranslatecmptype = hashtranslatecmptype; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index 66c39f60654..8cc1e079200 100644 --- a/src/backend/access/hash/hashutil.c +++ b/src/backend/access/hash/hashutil.c @@ -15,7 +15,7 @@ #include "postgres.h" #include "access/hash.h" -#include "access/reloptions.h" +#include "access/options.h" #include "access/relscan.h" #include "port/pg_bitutils.h" #include "utils/lsyscache.h" @@ -271,19 +271,6 @@ _hash_checkpage(Relation rel, Buffer buf, int flags) } } -bytea * -hashoptions(Datum reloptions, bool validate) -{ - static const relopt_parse_elt tab[] = { - {"fillfactor", RELOPT_TYPE_INT, offsetof(HashOptions, fillfactor)}, - }; - - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_HASH, - sizeof(HashOptions), - tab, lengthof(tab)); -} - /* * _hash_get_indextuple_hashkey - get the hash index tuple's hash key value */ @@ -619,3 +606,22 @@ _hash_kill_items(IndexScanDesc scan) else _hash_relbuf(rel, buf); } + +static options_spec_set *hash_relopt_specset = NULL; + +options_spec_set * +hashgetreloptspecset(void) +{ + if (hash_relopt_specset) + return hash_relopt_specset; + + hash_relopt_specset = allocateOptionsSpecSet(NULL, + sizeof(HashOptions), false, 1); + optionsSpecSetAddInt(hash_relopt_specset, "fillfactor", + "Packs hash index pages only to this percentage", + NoLock, /* No ALTER -- no lock */ + offsetof(HashOptions, fillfactor), NULL, + HASH_DEFAULT_FILLFACTOR, HASH_MIN_FILLFACTOR, 100); + + return hash_relopt_specset; +} diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index 971405e89af..67ec1fda939 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -21,6 +21,7 @@ #include "access/nbtree.h" #include "access/relscan.h" #include "access/stratnum.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "nodes/execnodes.h" @@ -134,7 +135,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; amroutine->amgettreeheight = btgettreeheight; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -151,6 +151,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amparallelrescan = btparallelrescan; amroutine->amtranslatestrategy = bttranslatestrategy; amroutine->amtranslatecmptype = bttranslatecmptype; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1551,3 +1552,35 @@ bttranslatecmptype(CompareType cmptype, Oid opfamily, Oid opcintype) return InvalidStrategy; } } + +static options_spec_set *bt_relopt_specset = NULL; + +options_spec_set * +btgetreloptspecset(void) +{ + if (bt_relopt_specset) + return bt_relopt_specset; + + bt_relopt_specset = allocateOptionsSpecSet(NULL, + sizeof(BTOptions), false, 3); + + optionsSpecSetAddInt(bt_relopt_specset, "fillfactor", + "Packs btree index pages only to this percentage", + ShareUpdateExclusiveLock, /* affects inserts only */ + offsetof(BTOptions, fillfactor), NULL, + BTREE_DEFAULT_FILLFACTOR, BTREE_MIN_FILLFACTOR, 100); + + optionsSpecSetAddReal(bt_relopt_specset, "vacuum_cleanup_index_scale_factor", + "Number of tuple inserts prior to index cleanup as a fraction of reltuples", + ShareUpdateExclusiveLock, + offsetof(BTOptions, vacuum_cleanup_index_scale_factor), + NULL, -1, 0.0, 1e10); + + optionsSpecSetAddBool(bt_relopt_specset, "deduplicate_items", + "Enables \"deduplicate items\" feature for this btree index", + ShareUpdateExclusiveLock, /* affects inserts only */ + offsetof(BTOptions, deduplicate_items), NULL, + true); + + return bt_relopt_specset; +} diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c index 693e43c674b..cf24295c5cd 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,8 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" +#include "access/relscan.h" #include "commands/progress.h" #include "miscadmin.h" #include "utils/datum.h" @@ -2715,23 +2716,6 @@ BTreeShmemInit(void) Assert(found); } -bytea * -btoptions(Datum reloptions, bool validate) -{ - static const relopt_parse_elt tab[] = { - {"fillfactor", RELOPT_TYPE_INT, offsetof(BTOptions, fillfactor)}, - {"vacuum_cleanup_index_scale_factor", RELOPT_TYPE_REAL, - offsetof(BTOptions, vacuum_cleanup_index_scale_factor)}, - {"deduplicate_items", RELOPT_TYPE_BOOL, - offsetof(BTOptions, deduplicate_items)} - }; - - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_BTREE, - sizeof(BTOptions), - tab, lengthof(tab)); -} - /* * btproperty() -- Check boolean properties of indexes. * diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c index 367c36ef9af..c203c174fb9 100644 --- a/src/backend/access/spgist/spgutils.c +++ b/src/backend/access/spgist/spgutils.c @@ -17,7 +17,7 @@ #include "access/amvalidate.h" #include "access/htup_details.h" -#include "access/reloptions.h" +#include "access/options.h" #include "access/spgist_private.h" #include "access/toast_compression.h" #include "access/transam.h" @@ -77,7 +77,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; amroutine->amgettreeheight = NULL; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -94,6 +93,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amparallelrescan = NULL; amroutine->amtranslatestrategy = NULL; amroutine->amtranslatecmptype = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -749,22 +749,6 @@ SpGistInitMetapage(Page page) ((char *) metadata + sizeof(SpGistMetaPageData)) - (char *) page; } -/* - * reloptions processing for SPGiST - */ -bytea * -spgoptions(Datum reloptions, bool validate) -{ - static const relopt_parse_elt tab[] = { - {"fillfactor", RELOPT_TYPE_INT, offsetof(SpGistOptions, fillfactor)}, - }; - - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_SPGIST, - sizeof(SpGistOptions), - tab, lengthof(tab)); -} - /* * Get the space needed to store a non-null datum of the indicated type * in an inner tuple (that is, as a prefix or node label). @@ -1363,3 +1347,24 @@ spgproperty(Oid index_oid, int attno, return true; } + +static options_spec_set *spgist_relopt_specset = NULL; + +options_spec_set * +spggetreloptspecset(void) +{ + + if (spgist_relopt_specset) + return spgist_relopt_specset; + + spgist_relopt_specset = allocateOptionsSpecSet(NULL, + sizeof(SpGistOptions), false, 1); + + optionsSpecSetAddInt(spgist_relopt_specset, "fillfactor", + "Packs spgist index pages only to this percentage", + ShareUpdateExclusiveLock, /* affects only inserts */ + offsetof(SpGistOptions, fillfactor), NULL, + SPGIST_DEFAULT_FILLFACTOR, SPGIST_MIN_FILLFACTOR, 100); + + return spgist_relopt_specset; +} diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 23cecd99c9e..a25b24af571 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -85,6 +85,7 @@ create_ctas_internal(List *attrList, IntoClause *into) Datum toast_options; const char *const validnsps[] = HEAP_RELOPT_NAMESPACES; ObjectAddress intoRelationAddr; + List *toastDefList; /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview = (into->viewQuery != NULL); @@ -119,14 +120,12 @@ create_ctas_internal(List *attrList, IntoClause *into) CommandCounterIncrement(); /* parse and validate reloptions for the toast table */ - toast_options = transformRelOptions((Datum) 0, - create->options, - "toast", - validnsps, - true, false); - (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true); + optionsDefListValdateNamespaces(create->options, validnsps); + toastDefList = optionsDefListFilterNamespaces(create->options, "toast"); + toast_options = optionDefListToTextArray(get_toast_relopt_spec_set(), + toastDefList); NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options); /* Create the "view" part of a materialized view. */ diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c index c14e038d54f..1c6426e6c57 100644 --- a/src/backend/commands/foreigncmds.c +++ b/src/backend/commands/foreigncmds.c @@ -112,7 +112,7 @@ transformGenericOptions(Oid catalogId, List *options, Oid fdwvalidator) { - List *resultOptions = untransformRelOptions(oldOptions); + List *resultOptions = optionsTextArrayToDefList(oldOptions); ListCell *optcell; Datum result; diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index f8d3ea820e1..9bfcffc39f2 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -20,6 +20,7 @@ #include "access/heapam.h" #include "access/htup_details.h" #include "access/reloptions.h" +#include "access/options.h" #include "access/sysattr.h" #include "access/tableam.h" #include "access/xact.h" @@ -570,8 +571,8 @@ DefineIndex(Oid tableId, IndexAmRoutine *amRoutine; bool amcanorder; bool amissummarizing; - amoptions_function amoptions; bool exclusion; + amreloptspecset_function amreloptspecsetfn; bool partitioned; bool safe_index; Datum reloptions; @@ -893,7 +894,7 @@ DefineIndex(Oid tableId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; amissummarizing = amRoutine->amsummarizing; pfree(amRoutine); @@ -908,10 +909,18 @@ DefineIndex(Oid tableId, /* * Parse AM-specific options, convert to text array form, validate. */ - reloptions = transformRelOptions((Datum) 0, stmt->options, - NULL, NULL, false, false); - (void) index_reloptions(amoptions, reloptions, true); + if (stmt->options && !amreloptspecsetfn) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("access method %s does not support options", + accessMethodName))); + + if (amreloptspecsetfn) + reloptions = optionDefListToTextArray(amreloptspecsetfn(), + stmt->options); + else + reloptions = (Datum) 0; /* * Prepare arguments for index_create, primarily an IndexInfo structure. @@ -2246,8 +2255,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, Assert(attn < nkeycols); opclassOptions[attn] = - transformRelOptions((Datum) 0, attribute->opclassopts, - NULL, NULL, false, false); + optionsDefListToTextArray(attribute->opclassopts); } else opclassOptions[attn] = (Datum) 0; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 5823fce9340..ea05e6307ed 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -21,6 +21,7 @@ #include "access/heapam_xlog.h" #include "access/multixact.h" #include "access/reloptions.h" +#include "access/options.h" #include "access/relscan.h" #include "access/sysattr.h" #include "access/tableam.h" @@ -741,7 +742,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - const char *const validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -890,19 +890,36 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, /* * Parse and validate reloptions, if any. */ - reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps, - true, false); switch (relkind) { case RELKIND_VIEW: - (void) view_reloptions(reloptions, true); + reloptions = optionDefListToTextArray(get_view_relopt_spec_set(), + stmt->options); break; case RELKIND_PARTITIONED_TABLE: - (void) partitioned_table_reloptions(reloptions, true); - break; + { + /* If it is not all listed above, then it if heap */ + const char *const namespaces[] = HEAP_RELOPT_NAMESPACES; + List *heapDefList; + + optionsDefListValdateNamespaces(stmt->options, namespaces); + heapDefList = optionsDefListFilterNamespaces(stmt->options, NULL); + reloptions = optionDefListToTextArray( + get_partitioned_relopt_spec_set(), heapDefList); + break; + } default: - (void) heap_reloptions(relkind, reloptions, true); + { + /* If it is not all listed above, is should be heap */ + const char *const namespaces[] = HEAP_RELOPT_NAMESPACES; + List *heapDefList; + + optionsDefListValdateNamespaces(stmt->options, namespaces); + heapDefList = optionsDefListFilterNamespaces(stmt->options, NULL); + reloptions = optionDefListToTextArray(get_heap_relopt_spec_set(), + heapDefList); + } } if (stmt->ofTypename) @@ -4526,7 +4543,7 @@ void AlterTableInternal(Oid relid, List *cmds, bool recurse) { Relation rel; - LOCKMODE lockmode = AlterTableGetLockLevel(cmds); + LOCKMODE lockmode = AlterTableGetLockLevel(relid, cmds); rel = relation_open(relid, lockmode); @@ -4568,7 +4585,7 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse) * otherwise we might end up with an inconsistent dump that can't restore. */ LOCKMODE -AlterTableGetLockLevel(List *cmds) +AlterTableGetLockLevel(Oid relid, List *cmds) { /* * This only works if we read catalog tables using MVCC snapshots. @@ -4789,9 +4806,14 @@ AlterTableGetLockLevel(List *cmds) * getTables() */ case AT_ResetRelOptions: /* Uses MVCC in getIndexes() and * getTables() */ - cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def); - break; + { + Relation rel = relation_open(relid, AccessShareLock); + cmd_lockmode = AlterTableGetRelOptionsLockLevel(rel, + castNode(List, cmd->def)); + relation_close(rel, AccessShareLock); + break; + } case AT_AttachPartition: cmd_lockmode = ShareUpdateExclusiveLock; break; @@ -8915,12 +8937,13 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options, /* Generate new proposed attoptions (text array) */ datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions, &isnull); - newOptions = transformRelOptions(isnull ? (Datum) 0 : datum, - castNode(List, options), NULL, NULL, - false, isReset); - /* Validate new options */ - (void) attribute_reloptions(newOptions, true); + if (isnull) + datum = (Datum) 0; + newOptions = optionsUpdateTexArrayWithDefList( + get_attribute_options_spec_set(), + datum, castNode(List, options), + isReset); /* Build new tuple. */ memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); @@ -15717,12 +15740,13 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, HeapTuple tuple; HeapTuple newtuple; Datum datum; - bool isnull; Datum newOptions; Datum repl_val[Natts_pg_class]; bool repl_null[Natts_pg_class]; bool repl_repl[Natts_pg_class]; - const char *const validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + const char *const heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -15742,38 +15766,56 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, * there were none before. */ datum = (Datum) 0; - isnull = true; } else { + bool isnull; + /* Get the old reloptions */ datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isnull); + if (isnull) + datum = (Datum) 0; } /* Generate new proposed reloptions (text array) */ - newOptions = transformRelOptions(isnull ? (Datum) 0 : datum, - defList, NULL, validnsps, false, - operation == AT_ResetRelOptions); /* Validate */ + + optionsSpecSet = NULL; switch (rel->rd_rel->relkind) { case RELKIND_RELATION: - case RELKIND_TOASTVALUE: case RELKIND_MATVIEW: - (void) heap_reloptions(rel->rd_rel->relkind, newOptions, true); + optionsDefListValdateNamespaces(defList, heap_namespaces); + optionsDefList = optionsDefListFilterNamespaces(defList, NULL); + optionsSpecSet = get_heap_relopt_spec_set(); break; case RELKIND_PARTITIONED_TABLE: - (void) partitioned_table_reloptions(newOptions, true); + optionsDefListValdateNamespaces(defList, heap_namespaces); + optionsDefList = optionsDefListFilterNamespaces(defList, NULL); + optionsSpecSet = get_partitioned_relopt_spec_set(); break; case RELKIND_VIEW: - (void) view_reloptions(newOptions, true); + optionsDefList = defList; + optionsSpecSet = get_view_relopt_spec_set(); break; case RELKIND_INDEX: case RELKIND_PARTITIONED_INDEX: - (void) index_reloptions(rel->rd_indam->amoptions, newOptions, true); + if (!rel->rd_indam->amreloptspecset) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("index %s does not support options", + RelationGetRelationName(rel)))); + optionsDefList = defList; + optionsSpecSet = rel->rd_indam->amreloptspecset(); break; + case RELKIND_TOASTVALUE: + /* Should never get here */ + /* TOAST options are never altered directly */ + Assert(0); + /* FALLTHRU */ + /* If we get here in prod. error is the best option */ default: ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -15783,11 +15825,15 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, break; } + newOptions = optionsUpdateTexArrayWithDefList(optionsSpecSet, datum, + optionsDefList, + operation == AT_ResetRelOptions); + /* Special-case validation of view options */ if (rel->rd_rel->relkind == RELKIND_VIEW) { Query *view_query = get_view_query(rel); - List *view_options = untransformRelOptions(newOptions); + List *view_options = optionsTextArrayToDefList(newOptions); ListCell *cell; bool check_option = false; @@ -15863,20 +15909,23 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, * pretend there were none before. */ datum = (Datum) 0; - isnull = true; } else { + bool isnull; + /* Get the old reloptions */ datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isnull); + if (isnull) + datum = (Datum) 0; } - newOptions = transformRelOptions(isnull ? (Datum) 0 : datum, - defList, "toast", validnsps, false, - operation == AT_ResetRelOptions); - - (void) heap_reloptions(RELKIND_TOASTVALUE, newOptions, true); + optionsDefList = optionsDefListFilterNamespaces(defList, "toast"); + newOptions = optionsUpdateTexArrayWithDefList( + get_toast_relopt_spec_set(), + datum, optionsDefList, + operation == AT_ResetRelOptions); memset(repl_val, 0, sizeof(repl_val)); memset(repl_null, false, sizeof(repl_null)); diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c index 4ac2763c7f3..96fc47a1061 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -333,10 +333,9 @@ CreateTableSpace(CreateTableSpaceStmt *stmt) nulls[Anum_pg_tablespace_spcacl - 1] = true; /* Generate new proposed spcoptions (text array) */ - newOptions = transformRelOptions((Datum) 0, - stmt->options, - NULL, NULL, false, false); - (void) tablespace_reloptions(newOptions, true); + newOptions = optionDefListToTextArray(get_tablespace_options_spec_set(), + stmt->options); + if (newOptions != (Datum) 0) values[Anum_pg_tablespace_spcoptions - 1] = newOptions; else @@ -1052,11 +1051,12 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt) /* Generate new proposed spcoptions (text array) */ datum = heap_getattr(tup, Anum_pg_tablespace_spcoptions, RelationGetDescr(rel), &isnull); - newOptions = transformRelOptions(isnull ? (Datum) 0 : datum, - stmt->options, NULL, NULL, false, - stmt->isReset); - (void) tablespace_reloptions(newOptions, true); + if (isnull) + datum = (Datum) 0; + newOptions = optionsUpdateTexArrayWithDefList( + get_tablespace_options_spec_set(), + datum, stmt->options, stmt->isReset); /* Build new tuple. */ memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c index f0835fc3070..87780f32309 100644 --- a/src/backend/foreign/foreign.c +++ b/src/backend/foreign/foreign.c @@ -80,7 +80,7 @@ GetForeignDataWrapperExtended(Oid fdwid, bits16 flags) if (isnull) fdw->options = NIL; else - fdw->options = untransformRelOptions(datum); + fdw->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -167,7 +167,7 @@ GetForeignServerExtended(Oid serverid, bits16 flags) if (isnull) server->options = NIL; else - server->options = untransformRelOptions(datum); + server->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -239,7 +239,7 @@ GetUserMapping(Oid userid, Oid serverid) if (isnull) um->options = NIL; else - um->options = untransformRelOptions(datum); + um->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -276,7 +276,7 @@ GetForeignTable(Oid relid) if (isnull) ft->options = NIL; else - ft->options = untransformRelOptions(datum); + ft->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -309,7 +309,7 @@ GetForeignColumnOptions(Oid relid, AttrNumber attnum) if (isnull) options = NIL; else - options = untransformRelOptions(datum); + options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -526,7 +526,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -624,7 +624,7 @@ is_conninfo_option(const char *option, Oid context) Datum postgresql_fdw_validator(PG_FUNCTION_ARGS) { - List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); + List *options_list = optionsTextArrayToDefList(PG_GETARG_DATUM(0)); Oid catalog = PG_GETARG_OID(1); ListCell *cell; diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index eb7716cd84c..9ff9b2b448e 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1923,7 +1923,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx, /* Add the operator class name, if non-default */ iparam->opclass = get_opclass(indclass->values[keyno], keycoltype); iparam->opclassopts = - untransformRelOptions(get_attoptions(source_relid, keyno + 1)); + optionsTextArrayToDefList(get_attoptions(source_relid, keyno + 1)); iparam->ordering = SORTBY_DEFAULT; iparam->nulls_ordering = SORTBY_NULLS_DEFAULT; @@ -1987,7 +1987,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx, datum = SysCacheGetAttr(RELOID, ht_idxrel, Anum_pg_class_reloptions, &isnull); if (!isnull) - index->options = untransformRelOptions(datum); + index->options = optionsTextArrayToDefList(datum); /* If it's a partial index, decompile and append the predicate */ datum = SysCacheGetAttr(INDEXRELID, ht_idx, diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 25fe3d58016..56cc463ee0e 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1159,6 +1159,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; const char *const validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1182,15 +1183,16 @@ ProcessUtilitySlow(ParseState *pstate, * parse and validate reloptions for the toast * table */ - toast_options = transformRelOptions((Datum) 0, - cstmt->options, - "toast", - validnsps, - true, - false); - (void) heap_reloptions(RELKIND_TOASTVALUE, - toast_options, - true); + + optionsDefListValdateNamespaces( + ((CreateStmt *) stmt)->options, + validnsps); + + toastDefList = optionsDefListFilterNamespaces( + ((CreateStmt *) stmt)->options, "toast"); + + toast_options = optionDefListToTextArray( + get_toast_relopt_spec_set(), toastDefList); NewRelationCreateToastTable(address.objectId, toast_options); @@ -1299,9 +1301,12 @@ ProcessUtilitySlow(ParseState *pstate, * lock on (for example) a relation on which we have no * permissions. */ - lockmode = AlterTableGetLockLevel(atstmt->cmds); - relid = AlterTableLookupRelation(atstmt, lockmode); - + relid = AlterTableLookupRelation(atstmt, AccessShareLock); + if (OidIsValid(relid)) + { + lockmode = AlterTableGetLockLevel(relid, atstmt->cmds); + relid = AlterTableLookupRelation(atstmt, lockmode); + } if (OidIsValid(relid)) { AlterTableUtilityContext atcontext; diff --git a/src/backend/utils/cache/attoptcache.c b/src/backend/utils/cache/attoptcache.c index 5c8360c08b5..f2850d5fc38 100644 --- a/src/backend/utils/cache/attoptcache.c +++ b/src/backend/utils/cache/attoptcache.c @@ -16,6 +16,7 @@ */ #include "postgres.h" +#include "access/options.h" #include "access/reloptions.h" #include "utils/attoptcache.h" #include "utils/catcache.h" @@ -176,7 +177,8 @@ get_attribute_options(Oid attrelid, int attnum) opts = NULL; else { - bytea *bytea_opts = attribute_reloptions(datum, false); + bytea *bytea_opts = optionsTextArrayToBytea( + get_attribute_options_spec_set(), datum, 0); opts = MemoryContextAlloc(CacheMemoryContext, VARSIZE(bytea_opts)); diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 398114373e9..6326c38d0df 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -465,7 +465,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -480,11 +480,11 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple) case RELKIND_VIEW: case RELKIND_MATVIEW: case RELKIND_PARTITIONED_TABLE: - amoptsfn = NULL; + amoptspecsetfn = NULL; break; case RELKIND_INDEX: case RELKIND_PARTITIONED_INDEX: - amoptsfn = relation->rd_indam->amoptions; + amoptspecsetfn = relation->rd_indam->amreloptspecset; break; default: return; @@ -495,7 +495,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple) * we might not have any other for pg_class yet (consider executing this * code for pg_class itself) */ - options = extractRelOptions(tuple, GetPgClassDescriptor(), amoptsfn); + options = extractRelOptions(tuple, GetPgClassDescriptor(), amoptspecsetfn); /* * Copy parsed data into CacheMemoryContext. To guard against the diff --git a/src/backend/utils/cache/spccache.c b/src/backend/utils/cache/spccache.c index 23458599298..3010692c442 100644 --- a/src/backend/utils/cache/spccache.c +++ b/src/backend/utils/cache/spccache.c @@ -149,7 +149,8 @@ get_tablespace(Oid spcid) opts = NULL; else { - bytea *bytea_opts = tablespace_reloptions(datum, false); + bytea *bytea_opts = optionsTextArrayToBytea( + get_tablespace_options_spec_set(), datum, 0); opts = MemoryContextAlloc(CacheMemoryContext, VARSIZE(bytea_opts)); memcpy(opts, bytea_opts, VARSIZE(bytea_opts)); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 6723de75a4d..1b1330bf879 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -15,6 +15,7 @@ #include "access/cmptype.h" #include "access/genam.h" #include "access/stratnum.h" +#include "access/options.h" /* * We don't wish to include planner header files here, since most of an index @@ -159,9 +160,9 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, */ typedef int (*amgettreeheight_function) (Relation rel); -/* parse index reloptions */ -typedef bytea *(*amoptions_function) (Datum reloptions, - bool validate); + +/* get Spec Set for relation options */ +typedef options_spec_set *(*amreloptspecset_function) (); /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, @@ -292,7 +293,7 @@ typedef struct IndexAmRoutine amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; amgettreeheight_function amgettreeheight; /* can be NULL */ - amoptions_function amoptions; + amreloptspecset_function amreloptspecset; /* can be NULL */ amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; diff --git a/src/include/access/brin.h b/src/include/access/brin.h index 821f1e02806..88434a55612 100644 --- a/src/include/access/brin.h +++ b/src/include/access/brin.h @@ -37,6 +37,8 @@ typedef struct BrinStatsData #define BRIN_DEFAULT_PAGES_PER_RANGE 128 +#define BRIN_MIN_PAGES_PER_RANGE 1 +#define BRIN_MAX_PAGES_PER_RANGE 131072 #define BrinGetPagesPerRange(relation) \ (AssertMacro(relation->rd_rel->relkind == RELKIND_INDEX && \ relation->rd_rel->relam == BRIN_AM_OID), \ diff --git a/src/include/access/brin_internal.h b/src/include/access/brin_internal.h index d093a0bf130..2513250fe6a 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -109,6 +109,7 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bytea *brinoptions(Datum reloptions, bool validate); +extern options_spec_set *bringetreloptspecset(void); /* brin_validate.c */ extern bool brinvalidate(Oid opclassoid); diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h index dcd1ae3fc34..3a15f747372 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -109,6 +109,7 @@ extern Datum *ginExtractEntries(GinState *ginstate, OffsetNumber attnum, extern OffsetNumber gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple); extern Datum gintuple_get_key(GinState *ginstate, IndexTuple tuple, GinNullCategory *category); +extern options_spec_set *gingetreloptspecset(void); /* gininsert.c */ extern IndexBuildResult *ginbuild(Relation heap, Relation index, diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h index 39404ec7cdb..7ed2230e0be 100644 --- a/src/include/access/gist_private.h +++ b/src/include/access/gist_private.h @@ -22,6 +22,7 @@ #include "storage/buffile.h" #include "utils/hsearch.h" #include "access/genam.h" +#include "access/options.h" /* * Maximum number of "halves" a page can be split into in one operation. @@ -388,6 +389,7 @@ typedef enum GistOptBufferingMode GIST_OPTION_BUFFERING_OFF, } GistOptBufferingMode; + /* * Storage type for GiST's reloptions */ @@ -479,7 +481,7 @@ extern void gistadjustmembers(Oid opfamilyoid, #define GIST_MIN_FILLFACTOR 10 #define GIST_DEFAULT_FILLFACTOR 90 -extern bytea *gistoptions(Datum reloptions, bool validate); +extern options_spec_set *gistgetreloptspecset(void); extern bool gistproperty(Oid index_oid, int attno, IndexAMProperty prop, const char *propname, bool *res, bool *isnull); diff --git a/src/include/access/hash.h b/src/include/access/hash.h index e91f2b00ad9..e8cc60bb799 100644 --- a/src/include/access/hash.h +++ b/src/include/access/hash.h @@ -380,7 +380,6 @@ extern IndexBulkDeleteResult *hashbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *hashvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *hashoptions(Datum reloptions, bool validate); extern bool hashvalidate(Oid opclassoid); extern void hashadjustmembers(Oid opfamilyoid, Oid opclassoid, @@ -477,6 +476,7 @@ extern BlockNumber _hash_get_newblock_from_oldbucket(Relation rel, Bucket old_bu extern Bucket _hash_get_newbucket_from_oldbucket(Relation rel, Bucket old_bucket, uint32 lowmask, uint32 maxbucket); extern void _hash_kill_items(IndexScanDesc scan); +extern options_spec_set *hashgetreloptspecset(void); /* hash.c */ extern void hashbucketcleanup(Relation rel, Bucket cur_bucket, diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h index 000c7289b80..271e1c82908 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1307,7 +1307,7 @@ extern void _bt_end_vacuum(Relation rel); extern void _bt_end_vacuum_callback(int code, Datum arg); extern Size BTreeShmemSize(void); extern void BTreeShmemInit(void); -extern bytea *btoptions(Datum reloptions, bool validate); +extern options_spec_set *btgetreloptspecset(void); extern bool btproperty(Oid index_oid, int attno, IndexAMProperty prop, const char *propname, bool *res, bool *isnull); diff --git a/src/include/access/options.h b/src/include/access/options.h new file mode 100644 index 00000000000..c1df04c90f4 --- /dev/null +++ b/src/include/access/options.h @@ -0,0 +1,274 @@ +/*------------------------------------------------------------------------- + * + * options.h + * An uniform, context-free API for processing name=value options. Used + * to process relation options (reloptions), attribute options, opclass + * options, etc. + * + * Note: the functions dealing with text-array options values declare + * them as Datum, not ArrayType *, to avoid needing to include array.h + * into a lot of low-level code. + * + * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * src/include/access/options.h + * + *------------------------------------------------------------------------- + */ + +#ifndef OPTIONS_H +#define OPTIONS_H + +#include "storage/lock.h" +#include "nodes/pg_list.h" + + +/* supported option types */ +typedef enum option_type +{ + OPTION_TYPE_BOOL, + OPTION_TYPE_INT, + OPTION_TYPE_REAL, + OPTION_TYPE_ENUM, + OPTION_TYPE_STRING, +} option_type; + +/* + * Each Option Value item passes through certain life cycle. Option Value + * Status specifies at what point of life cycle this Option Value is now. + * Option Value Statuses are: + * EMPTY - Option Value structure have been just created + * RAW - Option Value have been read, but has not been parsed or validated yet. + * Use raw_value structure member to access Raw Value + * PARSED - Option Value have been parsed, you should use proper union member + * to access Parsed Option Value, according to Option Type + * FOR_RESET - Specifies that this Option is designated for resetting to + * default value + */ +typedef enum option_value_status +{ + OPTION_VALUE_STATUS_EMPTY, /* Option was just initialized */ + OPTION_VALUE_STATUS_RAW, /* Option just came from syntax analyzer in + * has name, and raw (unparsed) value */ + OPTION_VALUE_STATUS_PARSED, /* Option was parsed and has link to Option + * Spec Set entry and proper value */ + OPTION_VALUE_STATUS_FOR_RESET, /* This option came from ALTER xxx RESET */ +} option_value_status; + +/* + * opt_enum_elt_def -- One member of the array of acceptable values + * of an enum reloption. + */ +typedef struct opt_enum_elt_def +{ + const char *string_val; + int symbol_val; +} opt_enum_elt_def; + + +typedef struct option_value option_value; + +/* Function that would be called after option validation. + * Might be needed for custom warnings, errors, or for changing + * option value after being validated, etc. + */ +typedef void (*option_value_postvalidate) (option_value *value); + +/* generic structure to store Option Spec information */ +typedef struct option_spec_basic +{ + const char *name; /* must be first (used as list termination + * marker) */ + const char *desc; + LOCKMODE lockmode; + option_type type; + int struct_offset; /* offset of the value in Bytea representation */ + option_value_postvalidate postvalidate_fn; +} option_spec_basic; + + +/* reloptions records for specific variable types */ +typedef struct option_spec_bool +{ + option_spec_basic base; + bool default_val; +} option_spec_bool; + +typedef struct option_spec_int +{ + option_spec_basic base; + int default_val; + int min; + int max; +} option_spec_int; + +typedef struct option_spec_real +{ + option_spec_basic base; + double default_val; + double min; + double max; +} option_spec_real; + +typedef struct option_spec_enum +{ + option_spec_basic base; + opt_enum_elt_def *members; /* Null terminated array of allowed names and + * corresponding values */ + int default_val; /* Default value, may differ from values in + * members array */ + const char *detailmsg; +} option_spec_enum; + +/* validation routines for strings */ +typedef void (*validate_string_option) (const char *value); +typedef Size (*fill_string_option) (const char *value, void *ptr); + +/* + * When storing sting reloptions, we should deal with special case when + * option value is not set. For fixed length options, we just copy default + * option value into the binary structure. For varlen value, there can be + * "not set" special case, with no default value offered. + * In this case we will set offset value to -1, so code that use reloptions + * can deal this case. For better readability it was defined as a constant. + */ +#define OPTION_STRING_VALUE_NOT_SET_OFFSET -1 + +typedef struct option_spec_string +{ + option_spec_basic base; + validate_string_option validate_cb; + fill_string_option fill_cb; + char *default_val; +} option_spec_string; + +typedef void (*postprocess_bytea_options_function) (void *data, bool validate); + +typedef struct options_spec_set +{ + option_spec_basic **definitions; + int num; /* Number of spec_set items in use */ + int num_allocated; /* Number of spec_set items allocated */ + bool assert_on_realloc; /* If number of items of the spec_set were + * strictly set to certain value assert on + * adding more items */ + bool is_local; /* If true specset is in local memory context */ + Size struct_size; /* Size of a structure for options in binary + * representation */ + postprocess_bytea_options_function postprocess_fun; /* This function is + * called after options + * were converted in + * Bytea representation. + * Can be used for extra + * validation etc. */ + char *namspace; /* Spec Set is used for options from this + * namespase */ +} options_spec_set; + + +/* holds an option value parsed or unparsed */ +typedef struct option_value +{ + option_spec_basic *gen; + char *namspace; + option_value_status status; + char *raw_value; /* allocated separately */ + char *raw_name; + union + { + bool bool_val; + int int_val; + double real_val; + int enum_val; + char *string_val; /* allocated separately */ + } values; +} option_value; + + +/* + * Options Spec Set related functions + */ +extern options_spec_set *allocateOptionsSpecSet(const char *namspace, + int bytea_size, bool is_local, int num_items_expected); + +extern void optionsSpecSetAddBool(options_spec_set *spec_set, const char *name, + const char *desc, LOCKMODE lockmode, + int struct_offset, + option_value_postvalidate postvalidate_fn, + bool default_val); + +extern void optionsSpecSetAddInt(options_spec_set *spec_set, const char *name, + const char *desc, LOCKMODE lockmode, + int struct_offset, + option_value_postvalidate postvalidate_fn, + int default_val, int min_val, int max_val); + +extern void optionsSpecSetAddReal(options_spec_set *spec_set, const char *name, + const char *desc, LOCKMODE lockmode, + int struct_offset, + option_value_postvalidate postvalidate_fn, + double default_val, double min_val, double max_val); + +extern void optionsSpecSetAddEnum(options_spec_set *spec_set, const char *name, + const char *desc, LOCKMODE lockmode, + int struct_offset, + option_value_postvalidate postvalidate_fn, + opt_enum_elt_def *members, int default_val, + const char *detailmsg); + + +extern void optionsSpecSetAddString(options_spec_set *spec_set, + const char *name, const char *desc, + LOCKMODE lockmode, int struct_offset, + option_value_postvalidate postvalidate_fn, + const char *default_val, + validate_string_option validator, + fill_string_option filler); + + +/* + * This macro allows to get string option value from bytea representation. + * "optstruct" - is a structure that is stored in bytea options representation + * "member" - member of this structure that has string option value + * (actually string values are stored in bytea after the structure, and + * and "member" will contain an offset to this value. This macro do all + * the math + */ +#define GET_STRING_OPTION(optstruct, member) \ + ((optstruct)->member == OPTION_STRING_VALUE_NOT_SET_OFFSET ? NULL : \ + (char *)(optstruct) + (optstruct)->member) + +/* + * Functions related to option conversion, parsing, manipulation + * and validation + */ +extern void optionsDefListValdateNamespaces(List *defList, + const char * const* allowed_namspaces); +extern List *optionsDefListFilterNamespaces(List *defList, + const char *namspace); +extern List *optionsTextArrayToDefList(Datum options); +extern Datum optionsDefListToTextArray(List *defList); + +/* + * Meta functions that uses functions above to get options for relations, + * tablespaces, views and so on + */ + +extern bytea *optionsTextArrayToBytea(options_spec_set *spec_set, Datum data, + bool validate); +extern Datum optionsUpdateTexArrayWithDefList(options_spec_set *spec_set, + Datum oldOptions, List *defList, bool do_reset); +extern Datum optionDefListToTextArray(options_spec_set *spec_set, + List *defList); + +/* Internal functions */ + +extern List *optionsTextArrayToRawValues(Datum array_datum); +extern List *optionsParseRawValues(List *raw_values, + options_spec_set *spec_set, bool validate); +extern bytea *optionsValuesToBytea(List *options, options_spec_set *spec_set); + + +#endif /* OPTIONS_H */ diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 43445cdcc6c..b42bb614e03 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -1,14 +1,9 @@ /*------------------------------------------------------------------------- * * reloptions.h - * Core support for relation and tablespace options (pg_class.reloptions + * Support for relation view and tablespace options (pg_class.reloptions * and pg_tablespace.spcoptions) * - * Note: the functions dealing with text-array reloptions values declare - * them as Datum, not ArrayType *, to avoid needing to include array.h - * into a lot of low-level code. - * - * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -25,175 +20,36 @@ #include "nodes/pg_list.h" #include "storage/lock.h" -/* types supported by reloptions */ -typedef enum relopt_type -{ - RELOPT_TYPE_BOOL, - RELOPT_TYPE_INT, - RELOPT_TYPE_REAL, - RELOPT_TYPE_ENUM, - RELOPT_TYPE_STRING, -} relopt_type; - -/* kinds supported by reloptions */ -typedef enum relopt_kind -{ - RELOPT_KIND_LOCAL = 0, - RELOPT_KIND_HEAP = (1 << 0), - RELOPT_KIND_TOAST = (1 << 1), - RELOPT_KIND_BTREE = (1 << 2), - RELOPT_KIND_HASH = (1 << 3), - RELOPT_KIND_GIN = (1 << 4), - RELOPT_KIND_GIST = (1 << 5), - RELOPT_KIND_ATTRIBUTE = (1 << 6), - RELOPT_KIND_TABLESPACE = (1 << 7), - RELOPT_KIND_SPGIST = (1 << 8), - RELOPT_KIND_VIEW = (1 << 9), - RELOPT_KIND_BRIN = (1 << 10), - RELOPT_KIND_PARTITIONED = (1 << 11), - /* if you add a new kind, make sure you update "last_default" too */ - RELOPT_KIND_LAST_DEFAULT = RELOPT_KIND_PARTITIONED, - /* some compilers treat enums as signed ints, so we can't use 1 << 31 */ - RELOPT_KIND_MAX = (1 << 30) -} relopt_kind; - /* reloption namespaces allowed for heaps -- currently only TOAST */ #define HEAP_RELOPT_NAMESPACES { "toast", NULL } -/* generic struct to hold shared data */ -typedef struct relopt_gen -{ - const char *name; /* must be first (used as list termination - * marker) */ - const char *desc; - bits32 kinds; - LOCKMODE lockmode; - int namelen; - relopt_type type; -} relopt_gen; - -/* holds a parsed value */ -typedef struct relopt_value -{ - relopt_gen *gen; - bool isset; - union - { - bool bool_val; - int int_val; - double real_val; - int enum_val; - char *string_val; /* allocated separately */ - } values; -} relopt_value; - -/* reloptions records for specific variable types */ -typedef struct relopt_bool -{ - relopt_gen gen; - bool default_val; -} relopt_bool; - -typedef struct relopt_int -{ - relopt_gen gen; - int default_val; - int min; - int max; -} relopt_int; - -typedef struct relopt_real -{ - relopt_gen gen; - double default_val; - double min; - double max; -} relopt_real; - /* - * relopt_enum_elt_def -- One member of the array of acceptable values - * of an enum reloption. + * backward compatibility aliases so local reloption code of custom validator + * can work. */ -typedef struct relopt_enum_elt_def -{ - const char *string_val; - int symbol_val; -} relopt_enum_elt_def; +typedef option_value relopt_value; +typedef fill_string_option fill_string_relopt; +typedef validate_string_option validate_string_relopt; +#define GET_STRING_RELOPTION(optstruct, member) \ + GET_STRING_OPTION(optstruct, member) -typedef struct relopt_enum -{ - relopt_gen gen; - relopt_enum_elt_def *members; - int default_val; - const char *detailmsg; - /* null-terminated array of members */ -} relopt_enum; -/* validation routines for strings */ -typedef void (*validate_string_relopt) (const char *value); -typedef Size (*fill_string_relopt) (const char *value, void *ptr); +/* + * relopts_validator functions is left for backward compatibility for using + * with local reloptions. Should not be used elsewhere + */ /* validation routine for the whole option set */ -typedef void (*relopts_validator) (void *parsed_options, relopt_value *vals, int nvals); - -typedef struct relopt_string -{ - relopt_gen gen; - int default_len; - bool default_isnull; - validate_string_relopt validate_cb; - fill_string_relopt fill_cb; - char *default_val; -} relopt_string; - -/* This is the table datatype for build_reloptions() */ -typedef struct -{ - const char *optname; /* option's name */ - relopt_type opttype; /* option's datatype */ - int offset; /* offset of field in result struct */ -} relopt_parse_elt; - -/* Local reloption definition */ -typedef struct local_relopt -{ - relopt_gen *option; /* option definition */ - int offset; /* offset of parsed value in bytea structure */ -} local_relopt; +typedef void (*relopts_validator) (void *parsed_options, relopt_value *vals, + int nvals); /* Structure to hold local reloption data for build_local_reloptions() */ typedef struct local_relopts { - List *options; /* list of local_relopt definitions */ List *validators; /* list of relopts_validator callbacks */ - Size relopt_struct_size; /* size of parsed bytea structure */ + options_spec_set *spec_set; /* Spec Set to store options info */ } local_relopts; -/* - * Utility macro to get a value for a string reloption once the options - * are parsed. This gets a pointer to the string value itself. "optstruct" - * is the StdRdOptions struct or equivalent, "member" is the struct member - * corresponding to the string option. - */ -#define GET_STRING_RELOPTION(optstruct, member) \ - ((optstruct)->member == 0 ? NULL : \ - (char *)(optstruct) + (optstruct)->member) - -extern relopt_kind add_reloption_kind(void); -extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, - bool default_val, LOCKMODE lockmode); -extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, - int default_val, int min_val, int max_val, - LOCKMODE lockmode); -extern void add_real_reloption(bits32 kinds, const char *name, const char *desc, - double default_val, double min_val, double max_val, - LOCKMODE lockmode); -extern void add_enum_reloption(bits32 kinds, const char *name, const char *desc, - relopt_enum_elt_def *members, int default_val, - const char *detailmsg, LOCKMODE lockmode); -extern void add_string_reloption(bits32 kinds, const char *name, const char *desc, - const char *default_val, validate_string_relopt validator, - LOCKMODE lockmode); extern void init_local_reloptions(local_relopts *relopts, Size relopt_struct_size); extern void register_reloptions_validator(local_relopts *relopts, @@ -210,7 +66,7 @@ extern void add_local_real_reloption(local_relopts *relopts, const char *name, int offset); extern void add_local_enum_reloption(local_relopts *relopts, const char *name, const char *desc, - relopt_enum_elt_def *members, + opt_enum_elt_def *members, int default_val, const char *detailmsg, int offset); extern void add_local_string_reloption(local_relopts *relopts, const char *name, @@ -219,29 +75,17 @@ extern void add_local_string_reloption(local_relopts *relopts, const char *name, validate_string_relopt validator, fill_string_relopt filler, int offset); -extern Datum transformRelOptions(Datum oldOptions, List *defList, - const char *namspace, const char *const validnsps[], - bool acceptOidsOff, bool isReset); -extern List *untransformRelOptions(Datum options); extern bytea *extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, - amoptions_function amoptions); -extern void *build_reloptions(Datum reloptions, bool validate, - relopt_kind kind, - Size relopt_struct_size, - const relopt_parse_elt *relopt_elems, - int num_relopt_elems); + amreloptspecset_function amoptions_def_set); extern void *build_local_reloptions(local_relopts *relopts, Datum options, bool validate); -extern bytea *default_reloptions(Datum reloptions, bool validate, - relopt_kind kind); -extern bytea *heap_reloptions(char relkind, Datum reloptions, bool validate); -extern bytea *view_reloptions(Datum reloptions, bool validate); -extern bytea *partitioned_table_reloptions(Datum reloptions, bool validate); -extern bytea *index_reloptions(amoptions_function amoptions, Datum reloptions, - bool validate); -extern bytea *attribute_reloptions(Datum reloptions, bool validate); -extern bytea *tablespace_reloptions(Datum reloptions, bool validate); -extern LOCKMODE AlterTableGetRelOptionsLockLevel(List *defList); +options_spec_set *get_heap_relopt_spec_set(void); +options_spec_set *get_toast_relopt_spec_set(void); +options_spec_set *get_partitioned_relopt_spec_set(void); +options_spec_set *get_view_relopt_spec_set(void); +options_spec_set *get_attribute_options_spec_set(void); +options_spec_set *get_tablespace_options_spec_set(void); +extern LOCKMODE AlterTableGetRelOptionsLockLevel(Relation rel, List *defList); #endif /* RELOPTIONS_H */ diff --git a/src/include/access/spgist.h b/src/include/access/spgist.h index cbe9b347d8f..c92c8698b98 100644 --- a/src/include/access/spgist.h +++ b/src/include/access/spgist.h @@ -189,9 +189,6 @@ typedef struct spgLeafConsistentOut } spgLeafConsistentOut; -/* spgutils.c */ -extern bytea *spgoptions(Datum reloptions, bool validate); - /* spginsert.c */ extern IndexBuildResult *spgbuild(Relation heap, Relation index, struct IndexInfo *indexInfo); diff --git a/src/include/access/spgist_private.h b/src/include/access/spgist_private.h index cb43a278f46..9f78a726646 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -530,6 +530,7 @@ extern OffsetNumber SpGistPageAddNewItem(SpGistState *state, Page page, extern bool spgproperty(Oid index_oid, int attno, IndexAMProperty prop, const char *propname, bool *res, bool *isnull); +extern options_spec_set *spggetreloptspecset(void); /* spgdoinsert.c */ extern void spgUpdateNodeLink(SpGistInnerTuple tup, int nodeN, diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index 6832470d387..03a656cb4a8 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -36,7 +36,7 @@ extern Oid AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode); extern void AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode, struct AlterTableUtilityContext *context); -extern LOCKMODE AlterTableGetLockLevel(List *cmds); +extern LOCKMODE AlterTableGetLockLevel(Oid relid, List *cmds); extern void ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lockmode); diff --git a/src/test/modules/dummy_index_am/dummy_index_am.c b/src/test/modules/dummy_index_am/dummy_index_am.c index 7586f8ae5e1..97467b73c92 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,18 +14,14 @@ #include "postgres.h" #include "access/amapi.h" -#include "access/reloptions.h" +#include "access/options.h" #include "catalog/index.h" #include "commands/vacuum.h" #include "nodes/pathnodes.h" PG_MODULE_MAGIC; -/* parse table for fillRelOptions */ -static relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -static relopt_kind di_relopt_kind; +void _PG_init(void); typedef enum DummyAmEnum { @@ -45,7 +41,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -static relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -59,77 +55,82 @@ PG_FUNCTION_INFO_V1(dihandler); * Validation function for string relation options. */ static void -validate_string_option(const char *value) +divalidate_string_option(const char *value) { ereport(NOTICE, (errmsg("new option value for string parameter %s", value ? value : "NULL"))); } -/* - * This function creates a full set of relation option types, - * with various patterns. - */ -static void -create_reloptions_table(void) +static options_spec_set *di_relopt_specset = NULL; +options_spec_set *digetreloptspecset(void); + +options_spec_set * +digetreloptspecset(void) { - di_relopt_kind = add_reloption_kind(); - - add_int_reloption(di_relopt_kind, "option_int", - "Integer option for dummy_index_am", - 10, -10, 100, AccessExclusiveLock); - di_relopt_tab[0].optname = "option_int"; - di_relopt_tab[0].opttype = RELOPT_TYPE_INT; - di_relopt_tab[0].offset = offsetof(DummyIndexOptions, option_int); - - add_real_reloption(di_relopt_kind, "option_real", - "Real option for dummy_index_am", - 3.1415, -10, 100, AccessExclusiveLock); - di_relopt_tab[1].optname = "option_real"; - di_relopt_tab[1].opttype = RELOPT_TYPE_REAL; - di_relopt_tab[1].offset = offsetof(DummyIndexOptions, option_real); - - add_bool_reloption(di_relopt_kind, "option_bool", - "Boolean option for dummy_index_am", - true, AccessExclusiveLock); - di_relopt_tab[2].optname = "option_bool"; - di_relopt_tab[2].opttype = RELOPT_TYPE_BOOL; - di_relopt_tab[2].offset = offsetof(DummyIndexOptions, option_bool); - - add_enum_reloption(di_relopt_kind, "option_enum", - "Enum option for dummy_index_am", - dummyAmEnumValues, - DUMMY_AM_ENUM_ONE, - "Valid values are \"one\" and \"two\".", - AccessExclusiveLock); - di_relopt_tab[3].optname = "option_enum"; - di_relopt_tab[3].opttype = RELOPT_TYPE_ENUM; - di_relopt_tab[3].offset = offsetof(DummyIndexOptions, option_enum); - - add_string_reloption(di_relopt_kind, "option_string_val", - "String option for dummy_index_am with non-NULL default", - "DefaultValue", &validate_string_option, - AccessExclusiveLock); - di_relopt_tab[4].optname = "option_string_val"; - di_relopt_tab[4].opttype = RELOPT_TYPE_STRING; - di_relopt_tab[4].offset = offsetof(DummyIndexOptions, - option_string_val_offset); + if (di_relopt_specset) + return di_relopt_specset; + + di_relopt_specset = allocateOptionsSpecSet(NULL, + sizeof(DummyIndexOptions), false, 6); + + optionsSpecSetAddInt( + di_relopt_specset, "option_int", + "Integer option for dummy_index_am", + AccessExclusiveLock, + offsetof(DummyIndexOptions, option_int), NULL, + 10, -10, 100 + ); + + + optionsSpecSetAddReal( + di_relopt_specset, "option_real", + "Real option for dummy_index_am", + AccessExclusiveLock, + offsetof(DummyIndexOptions, option_real), NULL, + 3.1415, -10, 100 + ); + + optionsSpecSetAddBool( + di_relopt_specset, "option_bool", + "Boolean option for dummy_index_am", + AccessExclusiveLock, + offsetof(DummyIndexOptions, option_bool), NULL, true + ); + + optionsSpecSetAddEnum(di_relopt_specset, "option_enum", + "Enum option for dummy_index_am", + AccessExclusiveLock, + offsetof(DummyIndexOptions, option_enum), NULL, + dummyAmEnumValues, + DUMMY_AM_ENUM_ONE, + "Valid values are \"one\" and \"two\"." + ); + + optionsSpecSetAddString(di_relopt_specset, "option_string_val", + "String option for dummy_index_am with non-NULL default", + AccessExclusiveLock, + offsetof(DummyIndexOptions, option_string_val_offset), NULL, + "DefaultValue", &divalidate_string_option, NULL + ); /* * String option for dummy_index_am with NULL default, and without * description. */ - add_string_reloption(di_relopt_kind, "option_string_null", - NULL, /* description */ - NULL, &validate_string_option, - AccessExclusiveLock); - di_relopt_tab[5].optname = "option_string_null"; - di_relopt_tab[5].opttype = RELOPT_TYPE_STRING; - di_relopt_tab[5].offset = offsetof(DummyIndexOptions, - option_string_null_offset); + + optionsSpecSetAddString(di_relopt_specset, "option_string_null", + NULL, /* description */ + AccessExclusiveLock, + offsetof(DummyIndexOptions, option_string_null_offset), NULL, + NULL, &divalidate_string_option, NULL + ); + + return di_relopt_specset; } + /* * Build a new index. */ @@ -214,19 +215,6 @@ dicostestimate(PlannerInfo *root, IndexPath *path, double loop_count, *indexPages = 1; } -/* - * Parse relation options for index AM, returning a DummyIndexOptions - * structure filled with option values. - */ -static bytea * -dioptions(Datum reloptions, bool validate) -{ - return (bytea *) build_reloptions(reloptions, validate, - di_relopt_kind, - sizeof(DummyIndexOptions), - di_relopt_tab, lengthof(di_relopt_tab)); -} - /* * Validator for index AM. */ @@ -307,7 +295,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; amroutine->amgettreeheight = NULL; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -321,12 +308,7 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = digetreloptspecset; PG_RETURN_POINTER(amroutine); } - -void -_PG_init(void) -{ - create_reloptions_table(); -} diff --git a/src/test/modules/test_oat_hooks/expected/alter_table.out b/src/test/modules/test_oat_hooks/expected/alter_table.out index 8cbacca2c9b..8319cd06941 100644 --- a/src/test/modules/test_oat_hooks/expected/alter_table.out +++ b/src/test/modules/test_oat_hooks/expected/alter_table.out @@ -82,6 +82,8 @@ ALTER TABLE test_oat_schema.test_oat_tab ENABLE ROW LEVEL SECURITY; NOTICE: in process utility: superuser attempting ALTER TABLE NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser finished alter (subId=0x0) [explicit without auxiliary object] NOTICE: in process utility: superuser finished ALTER TABLE @@ -89,6 +91,8 @@ ALTER TABLE test_oat_schema.test_oat_tab DISABLE ROW LEVEL SECURITY; NOTICE: in process utility: superuser attempting ALTER TABLE NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser finished alter (subId=0x0) [explicit without auxiliary object] NOTICE: in process utility: superuser finished ALTER TABLE @@ -96,6 +100,8 @@ ALTER TABLE test_oat_schema.test_oat_tab FORCE ROW LEVEL SECURITY; NOTICE: in process utility: superuser attempting ALTER TABLE NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser finished alter (subId=0x0) [explicit without auxiliary object] NOTICE: in process utility: superuser finished ALTER TABLE @@ -103,6 +109,8 @@ ALTER TABLE test_oat_schema.test_oat_tab NO FORCE ROW LEVEL SECURITY; NOTICE: in process utility: superuser attempting ALTER TABLE NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser finished alter (subId=0x0) [explicit without auxiliary object] NOTICE: in process utility: superuser finished ALTER TABLE @@ -111,6 +119,8 @@ ALTER TABLE test_oat_schema.test_oat_tab DISABLE RULE test_oat_notify; NOTICE: in process utility: superuser attempting ALTER TABLE NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser finished alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] @@ -120,6 +130,8 @@ ALTER TABLE test_oat_schema.test_oat_tab ENABLE RULE test_oat_notify; NOTICE: in process utility: superuser attempting ALTER TABLE NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser finished alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] @@ -130,6 +142,8 @@ ALTER TABLE test_oat_schema.test_oat_tab DISABLE TRIGGER test_oat_trigger; NOTICE: in process utility: superuser attempting ALTER TABLE NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser finished alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] @@ -139,6 +153,8 @@ ALTER TABLE test_oat_schema.test_oat_tab ENABLE TRIGGER test_oat_trigger; NOTICE: in process utility: superuser attempting ALTER TABLE NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser finished alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..67f0a828f9f 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -175,7 +175,7 @@ SELECT reloptions FROM pg_class WHERE oid = :toast_oid; -- Fail on non-existent options in toast namespace CREATE TABLE reloptions_test2 (i int) WITH (toast.not_existing_option = 42); -ERROR: unrecognized parameter "not_existing_option" +ERROR: unrecognized parameter "toast.not_existing_option" -- Mix TOAST & heap DROP TABLE reloptions_test; CREATE TABLE reloptions_test (s VARCHAR) WITH @@ -194,6 +194,17 @@ SELECT reloptions FROM pg_class WHERE oid = ( {autovacuum_vacuum_cost_delay=23} (1 row) +-- Can reset option that is not allowed, but for some reason is already set +UPDATE pg_class + SET reloptions = '{fillfactor=13,autovacuum_enabled=false,illegal_option=4}' + WHERE oid = 'reloptions_test'::regclass; +ALTER TABLE reloptions_test RESET (illegal_option); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------------------------ + {fillfactor=13,autovacuum_enabled=false} +(1 row) + -- -- CREATE INDEX, ALTER INDEX for btrees -- diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..9b404295c05 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -112,6 +112,13 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; SELECT reloptions FROM pg_class WHERE oid = ( SELECT reltoastrelid FROM pg_class WHERE oid = 'reloptions_test'::regclass); +-- Can reset option that is not allowed, but for some reason is already set +UPDATE pg_class + SET reloptions = '{fillfactor=13,autovacuum_enabled=false,illegal_option=4}' + WHERE oid = 'reloptions_test'::regclass; +ALTER TABLE reloptions_test RESET (illegal_option); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- -- CREATE INDEX, ALTER INDEX for btrees -- -- 2.39.2 [application/pgp-signature] signature.asc (488B, ../../2220883.Mh6RI2rZIc@thinkpad-pgpro/3-signature.asc) download ^ permalink raw reply [nested|flat] 3+ messages in thread
* Re: [PATCH] New [relation] option engine 2025-02-10 18:35 Re: [PATCH] New [relation] option engine Nikolay Shaplov <[email protected]> @ 2025-02-26 19:33 ` Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 3+ messages in thread From: Nikolay Shaplov @ 2025-02-26 19:33 UTC (permalink / raw) To: jian he <[email protected]>; [email protected]; +Cc: Peter Smith <[email protected]>; Alvaro Herrera <[email protected]>; Yura Sokolov <[email protected]>; [email protected]; Michael Paquier <[email protected]>; Nikolay Shaplov <[email protected]> В письме от понедельник, 10 февраля 2025 г. 21:35:40 MSK пользователь Nikolay Shaplov написал: And new rebase: Now I added vacuum_max_eager_freeze_failure_rate option introduced in 052026c commit. -- Nikolay Shaplov aka Nataraj Fuzzing Engineer at Postgres Professional Matrix IM: @dhyan:nataraj.su Attachments: [text/x-patch] v-11-0001-New-options-engine.patch (207.3K, ../../39561561.10thIPus4b@thinkpad-pgpro/2-v-11-0001-New-options-engine.patch) download | inline diff: From 26d0946110b439aa44ce82e128f613079b8b7f69 Mon Sep 17 00:00:00 2001 From: Nikolay Shaplov <[email protected]> Date: Wed, 26 Feb 2025 22:24:29 +0300 Subject: [PATCH v[11] 1/1] New options engine Add new options engine that will allow to define options directly in any part of code they are needed (anywhere, not only for relation and op. classes) and use unified API for option processing. This will allow: - Unify options code. Now we have core AM reloptions, contrib AM reloptions and local reloptions for op. classes, each using their own API. New engine will allow to use single API for them all. - Move options definition completely to Access Method implementation. Done for index AM, will be done storage AM later. (Before core AM all options were defined in reloptions.c) - All options for AM is defined in one single chunk of code. (Before for core AM you had to edit both reloptions.c and code of AM, this was error prone) - More simple way to define new option stets (we will need partitioned.* options sooner or later, this engine will allow to do it more easy) - Allow to add name=value options anywhere they needed. - More flexible way for defining option behavior (e.g. disable OIDS=true via postvalidate callback, without changing option processing code) What is done, and what is still in progress: - Index AM options now using new option engine the way it should be. - Options for Heap and Toast, are also using new engine, but they are still located in reloptions.c file as they were before. They will be moved to storage AM later. - local_options API that is used for op. class options is now wrapper around new option engine. Later on, build in op. classes will be updated to use new option engine directly, and local_options API can be kept for backward compatibility for a while. This patch does not change postgres behavior. All existing options work the way they did it before. One single error message is changed, and it is more accurate now. ``` -ERROR: unrecognized parameter "not_existing_option" +ERROR: unrecognized parameter "toast.not_existing_option" ``` General concepts of the new engine: - `option_spec` is a C-structure that completely describes single option, how it should be parsed, validated and stored: name, type, acceptable values, offset in bytea representation, postvalidate call back, etc. We have `option_spec_*` for each option type. - `options_spec_set` is a set of `option_spec`s that are available for certain context (e.g. for certain AM reloptions). It is a list of option_spec`s plus a callback for overall validation (e.g. make sure optionA < optionB) - There are four representations of options. The way they came from SQL parser (defList), the way they are stored in DB (TEXT[]), the binary representation that is used by outer code (bytea), and internal representation that is used by option engine while parsing and validating options (Option Value List) - There are functions to convert options from one representation to another (possibly with validation if applicable): `optionsTextArrayToDefList`, `optionsDefListToTextArray`, `optionsTextArrayToBytea` and others. - There are functions for defining `option_spec_set`: `allocateOptionsSpecSet`, `optionsSpecSetAddBool`, `optionsSpecSetAddInt`, etc. Outer code, that would like to use option engine, should define it's own `option_spec_set`, implement getting options from SQL parser, getting and putting them to DB storage, passing binary representation of the option to the code that would actually use them. For parsing, validating and converting options from one representation to another, outer code should use functions from options engine function set. Each function will use information from `option_spec_set` to determinate what options are available and how exactly they should be parsed and validated. This is only a start of option code reworking. Further improvements listed above and other suggestion will follow. --- contrib/bloom/bloom.h | 4 +- contrib/bloom/blutils.c | 102 +- contrib/dblink/dblink.c | 2 +- contrib/file_fdw/file_fdw.c | 2 +- contrib/postgres_fdw/option.c | 2 +- contrib/test_decoding/expected/twophase.out | 3 +- doc/src/sgml/indexam.sgml | 25 +- src/backend/access/brin/brin.c | 46 +- src/backend/access/common/Makefile | 1 + src/backend/access/common/meson.build | 1 + src/backend/access/common/options.c | 1367 ++++++++++ src/backend/access/common/reloptions.c | 2278 ++++------------- src/backend/access/gin/ginutil.c | 46 +- src/backend/access/gist/gist.c | 2 +- src/backend/access/gist/gistutil.c | 52 +- src/backend/access/hash/hash.c | 2 +- src/backend/access/hash/hashutil.c | 34 +- src/backend/access/nbtree/nbtree.c | 35 +- src/backend/access/nbtree/nbtutils.c | 20 +- src/backend/access/spgist/spgutils.c | 41 +- src/backend/commands/createas.c | 11 +- src/backend/commands/foreigncmds.c | 2 +- src/backend/commands/indexcmds.c | 22 +- src/backend/commands/tablecmds.c | 117 +- src/backend/commands/tablespace.c | 16 +- src/backend/foreign/foreign.c | 14 +- src/backend/parser/parse_utilcmd.c | 4 +- src/backend/tcop/utility.c | 29 +- src/backend/utils/cache/attoptcache.c | 4 +- src/backend/utils/cache/relcache.c | 8 +- src/backend/utils/cache/spccache.c | 3 +- src/include/access/amapi.h | 9 +- src/include/access/brin.h | 2 + src/include/access/brin_internal.h | 1 + src/include/access/gin_private.h | 1 + src/include/access/gist_private.h | 4 +- src/include/access/hash.h | 2 +- src/include/access/nbtree.h | 2 +- src/include/access/options.h | 274 ++ src/include/access/reloptions.h | 204 +- src/include/access/spgist.h | 3 - src/include/access/spgist_private.h | 1 + src/include/commands/tablecmds.h | 2 +- .../modules/dummy_index_am/dummy_index_am.c | 148 +- .../test_oat_hooks/expected/alter_table.out | 16 + src/test/regress/expected/reloptions.out | 13 +- src/test/regress/sql/reloptions.sql | 7 + 47 files changed, 2630 insertions(+), 2354 deletions(-) create mode 100644 src/backend/access/common/options.c create mode 100644 src/include/access/options.h diff --git a/contrib/bloom/bloom.h b/contrib/bloom/bloom.h index 648167045f4..47f697f66a8 100644 --- a/contrib/bloom/bloom.h +++ b/contrib/bloom/bloom.h @@ -17,6 +17,7 @@ #include "access/generic_xlog.h" #include "access/itup.h" #include "access/xlog.h" +#include "access/options.h" #include "fmgr.h" #include "nodes/pathnodes.h" @@ -203,7 +204,8 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *bloptions(Datum reloptions, bool validate); +extern options_spec_set *blrelopt_specset(void); +extern void blReloptionPostprocess(void *, bool validate); extern void blcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, Cost *indexStartupCost, Cost *indexTotalCost, Selectivity *indexSelectivity, diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c index 04b61042a57..b4f55daf49b 100644 --- a/contrib/bloom/blutils.c +++ b/contrib/bloom/blutils.c @@ -15,7 +15,7 @@ #include "access/amapi.h" #include "access/generic_xlog.h" -#include "access/reloptions.h" +#include "access/options.h" #include "bloom.h" #include "commands/vacuum.h" #include "storage/bufmgr.h" @@ -31,52 +31,12 @@ PG_FUNCTION_INFO_V1(blhandler); -/* Kind of relation options for bloom index */ -static relopt_kind bl_relopt_kind; - -/* parse table for fillRelOptions */ -static relopt_parse_elt bl_relopt_tab[INDEX_MAX_KEYS + 1]; +/* Catalog of relation options for bloom index */ +static options_spec_set *bl_relopt_specset; static int32 myRand(void); static void mySrand(uint32 seed); -/* - * Module initialize function: initialize info about Bloom relation options. - * - * Note: keep this in sync with makeDefaultBloomOptions(). - */ -void -_PG_init(void) -{ - int i; - char buf[16]; - - bl_relopt_kind = add_reloption_kind(); - - /* Option for length of signature */ - add_int_reloption(bl_relopt_kind, "length", - "Length of signature in bits", - DEFAULT_BLOOM_LENGTH, 1, MAX_BLOOM_LENGTH, - AccessExclusiveLock); - bl_relopt_tab[0].optname = "length"; - bl_relopt_tab[0].opttype = RELOPT_TYPE_INT; - bl_relopt_tab[0].offset = offsetof(BloomOptions, bloomLength); - - /* Number of bits for each possible index column: col1, col2, ... */ - for (i = 0; i < INDEX_MAX_KEYS; i++) - { - snprintf(buf, sizeof(buf), "col%d", i + 1); - add_int_reloption(bl_relopt_kind, buf, - "Number of bits generated for each index column", - DEFAULT_BLOOM_BITS, 1, MAX_BLOOM_BITS, - AccessExclusiveLock); - bl_relopt_tab[i + 1].optname = MemoryContextStrdup(TopMemoryContext, - buf); - bl_relopt_tab[i + 1].opttype = RELOPT_TYPE_INT; - bl_relopt_tab[i + 1].offset = offsetof(BloomOptions, bitSize[0]) + sizeof(int) * i; - } -} - /* * Construct a default set of Bloom options. */ @@ -135,7 +95,7 @@ blhandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; amroutine->amgettreeheight = NULL; - amroutine->amoptions = bloptions; + amroutine->amreloptspecset = blrelopt_specset; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = blvalidate; @@ -156,6 +116,15 @@ blhandler(PG_FUNCTION_ARGS) PG_RETURN_POINTER(amroutine); } +void +blReloptionPostprocess(void *data, bool validate) +{ + BloomOptions *opts = (BloomOptions *) data; + /* Convert signature length from # of bits to # to words, rounding up */ + opts->bloomLength = (opts->bloomLength + SIGNWORDBITS - 1) / SIGNWORDBITS; +} + + /* * Fill BloomState structure for particular index. */ @@ -470,24 +439,37 @@ BloomInitMetapage(Relation index, ForkNumber forknum) UnlockReleaseBuffer(metaBuffer); } -/* - * Parse reloptions for bloom index, producing a BloomOptions struct. - */ -bytea * -bloptions(Datum reloptions, bool validate) +options_spec_set * +blrelopt_specset(void) { - BloomOptions *rdopts; + int i; + char buf[16]; - /* Parse the user-given reloptions */ - rdopts = (BloomOptions *) build_reloptions(reloptions, validate, - bl_relopt_kind, - sizeof(BloomOptions), - bl_relopt_tab, - lengthof(bl_relopt_tab)); + if (bl_relopt_specset) + return bl_relopt_specset; - /* Convert signature length from # of bits to # to words, rounding up */ - if (rdopts) - rdopts->bloomLength = (rdopts->bloomLength + SIGNWORDBITS - 1) / SIGNWORDBITS; - return (bytea *) rdopts; + bl_relopt_specset = allocateOptionsSpecSet(NULL, + sizeof(BloomOptions), false, INDEX_MAX_KEYS + 1); + bl_relopt_specset->postprocess_fun = blReloptionPostprocess; + + optionsSpecSetAddInt(bl_relopt_specset, "length", + "Length of signature in bits", + NoLock, /* No lock as far as ALTER is not + * effective */ + offsetof(BloomOptions, bloomLength), NULL, + DEFAULT_BLOOM_LENGTH, 1, MAX_BLOOM_LENGTH); + + /* Number of bits for each possible index column: col1, col2, ... */ + for (i = 0; i < INDEX_MAX_KEYS; i++) + { + snprintf(buf, 16, "col%d", i + 1); + optionsSpecSetAddInt(bl_relopt_specset, buf, + "Number of bits for corresponding column", + NoLock, /* No lock as far as ALTER is not + * effective */ + offsetof(BloomOptions, bitSize[i]), NULL, + DEFAULT_BLOOM_BITS, 1, MAX_BLOOM_BITS); + } + return bl_relopt_specset; } diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c index bed2dee3d72..bdd4d83fdf0 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -1938,7 +1938,7 @@ PG_FUNCTION_INFO_V1(dblink_fdw_validator); Datum dblink_fdw_validator(PG_FUNCTION_ARGS) { - List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); + List *options_list = optionsTextArrayToDefList(PG_GETARG_DATUM(0)); Oid context = PG_GETARG_OID(1); ListCell *cell; diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c index 0655bf532a0..95b8b1bd264 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -201,7 +201,7 @@ file_fdw_handler(PG_FUNCTION_ARGS) Datum file_fdw_validator(PG_FUNCTION_ARGS) { - List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); + List *options_list = optionsTextArrayToDefList(PG_GETARG_DATUM(0)); Oid catalog = PG_GETARG_OID(1); char *filename = NULL; DefElem *force_not_null = NULL; diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c index d0766f007d2..085a1228951 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -70,7 +70,7 @@ PG_FUNCTION_INFO_V1(postgres_fdw_validator); Datum postgres_fdw_validator(PG_FUNCTION_ARGS) { - List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); + List *options_list = optionsTextArrayToDefList(PG_GETARG_DATUM(0)); Oid catalog = PG_GETARG_OID(1); ListCell *cell; diff --git a/contrib/test_decoding/expected/twophase.out b/contrib/test_decoding/expected/twophase.out index 08a7c56b5df..98956c5e22f 100644 --- a/contrib/test_decoding/expected/twophase.out +++ b/contrib/test_decoding/expected/twophase.out @@ -69,9 +69,10 @@ WHERE locktype = 'relation' AND relation = 'test_prepared1'::regclass; relation | locktype | mode -----------------+----------+--------------------- + test_prepared_1 | relation | AccessShareLock test_prepared_1 | relation | RowExclusiveLock test_prepared_1 | relation | AccessExclusiveLock -(2 rows) +(3 rows) -- The insert should show the newly altered column but not the DDL. SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml index d17fcbd5cec..af7004dd76c 100644 --- a/doc/src/sgml/indexam.sgml +++ b/doc/src/sgml/indexam.sgml @@ -500,27 +500,12 @@ amgettreeheight (Relation rel); <para> <programlisting> -bytea * -amoptions (ArrayType *reloptions, - bool validate); +void * +amreloptspecset(void); </programlisting> - Parse and validate the reloptions array for an index. This is called only - when a non-null reloptions array exists for the index. - <parameter>reloptions</parameter> is a <type>text</type> array containing entries of the - form <replaceable>name</replaceable><literal>=</literal><replaceable>value</replaceable>. - The function should construct a <type>bytea</type> value, which will be copied - into the <structfield>rd_options</structfield> field of the index's relcache entry. - The data contents of the <type>bytea</type> value are open for the access - method to define; most of the standard access methods use struct - <structname>StdRdOptions</structname>. - When <parameter>validate</parameter> is true, the function should report a suitable - error message if any of the options are unrecognized or have invalid - values; when <parameter>validate</parameter> is false, invalid entries should be - silently ignored. (<parameter>validate</parameter> is false when loading options - already stored in <structname>pg_catalog</structname>; an invalid entry could only - be found if the access method has changed its rules for options, and in - that case ignoring obsolete entries is appropriate.) - It is OK to return NULL if default behavior is wanted. + Returns pointer to Options Spec Set that defines options available for an + index relation. This Spec Set will be used for parsing and validating index + relation options. </para> <para> diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 60320440fc5..78358a97043 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -20,7 +20,6 @@ #include "access/brin_pageops.h" #include "access/brin_xlog.h" #include "access/relation.h" -#include "access/reloptions.h" #include "access/relscan.h" #include "access/table.h" #include "access/tableam.h" @@ -283,7 +282,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; amroutine->amgettreeheight = NULL; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -300,6 +298,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amparallelrescan = NULL; amroutine->amtranslatestrategy = NULL; amroutine->amtranslatecmptype = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1336,23 +1335,6 @@ brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats) return stats; } -/* - * reloptions processor for BRIN indexes - */ -bytea * -brinoptions(Datum reloptions, bool validate) -{ - static const relopt_parse_elt tab[] = { - {"pages_per_range", RELOPT_TYPE_INT, offsetof(BrinOptions, pagesPerRange)}, - {"autosummarize", RELOPT_TYPE_BOOL, offsetof(BrinOptions, autosummarize)} - }; - - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_BRIN, - sizeof(BrinOptions), - tab, lengthof(tab)); -} - /* * SQL-callable function to scan through an index and summarize all ranges * that are not currently summarized. @@ -3011,3 +2993,29 @@ brin_fill_empty_ranges(BrinBuildState *state, blkno += state->bs_pagesPerRange; } } + +static options_spec_set *brin_relopt_specset = NULL; + +options_spec_set * +bringetreloptspecset(void) +{ + if (brin_relopt_specset) + return brin_relopt_specset; + brin_relopt_specset = allocateOptionsSpecSet(NULL, + sizeof(BrinOptions), false, 2); + + optionsSpecSetAddInt(brin_relopt_specset, "pages_per_range", + "Number of pages that each page range covers in a BRIN index", + NoLock, /* since ALTER is not allowed no lock + * needed */ + offsetof(BrinOptions, pagesPerRange), NULL, + BRIN_DEFAULT_PAGES_PER_RANGE, + BRIN_MIN_PAGES_PER_RANGE, + BRIN_MAX_PAGES_PER_RANGE); + optionsSpecSetAddBool(brin_relopt_specset, "autosummarize", + "Enables automatic summarization on this BRIN index", + AccessExclusiveLock, + offsetof(BrinOptions, autosummarize), NULL, + false); + return brin_relopt_specset; +} diff --git a/src/backend/access/common/Makefile b/src/backend/access/common/Makefile index e78de312659..014d1b15622 100644 --- a/src/backend/access/common/Makefile +++ b/src/backend/access/common/Makefile @@ -18,6 +18,7 @@ OBJS = \ detoast.o \ heaptuple.o \ indextuple.o \ + options.o \ printsimple.o \ printtup.o \ relation.o \ diff --git a/src/backend/access/common/meson.build b/src/backend/access/common/meson.build index e3cdbe7a22e..9f9bbc494cc 100644 --- a/src/backend/access/common/meson.build +++ b/src/backend/access/common/meson.build @@ -6,6 +6,7 @@ backend_sources += files( 'detoast.c', 'heaptuple.c', 'indextuple.c', + 'options.c', 'printsimple.c', 'printtup.c', 'relation.c', diff --git a/src/backend/access/common/options.c b/src/backend/access/common/options.c new file mode 100644 index 00000000000..fc42a02b573 --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1367 @@ +/*------------------------------------------------------------------------- + * + * options.c + * An uniform, context-free API for processing name=value options. Used + * to process relation options (reloptions), attribute options, opclass + * options, etc. + * + * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/common/options.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/options.h" +#include "catalog/pg_type.h" +#include "commands/defrem.h" +#include "nodes/makefuncs.h" +#include "utils/builtins.h" +#include "utils/guc.h" +#include "utils/memutils.h" +#include "mb/pg_wchar.h" + + +/* + * OPTIONS SPECIFICATION and OPTION SPECIFICATION SET + * + * Each option is defined via Option Specification object (Option Spec). + * Option Spec should have all information that is needed for processing + * (parsing, validating, converting) of a single option. Implemented via set of + * option_spec_* structures. + * + * A set of Option Specs (Options Spec Set), defines all options available for + * certain object (certain relation kind for example). It is a list of + * Options Specs, plus validation functions that can be used to validate whole + * option set, if needed. Implemented via options_spec_set structure and set of + * optionsSpecSetAdd* functions that are used for adding Option Specs items to + * a Set. + * + * NOTE: we choose term "specification" instead of "definition" because term + * "definition" is used for objects that came from syntax parser. So to avoid + * confusion here we have Option Specifications, and all "definitions" are from + * parser. + */ + +/* + * OPTION VALUES REPRESENTATIONS + * + * Option values usually came from syntax parser in form of defList object, + * stored in pg_catalog as text array, and used when they are stored in memory + * as C-structure. These are different option values representations. Here goes + * brief description of all representations used in the code. + * + * Value List + * + * Value List is an internal representation that is used while converting + * option values between different representation. Value List item is called + * "parsed", when Value's value is converted to a proper data type and + * validated, or is called "unparsed", when Value's value is stored as raw + * string that was obtained from the source without any checks. In conversion + * function names first case is referred as Values, second case is referred as + * RawValues. Value List is implemented as List of option_value C-structures. + * + * defList + * + * Options in form of definition List that comes from syntax parser. (For + * reloptions it is a part of SQL query that goes after WITH, SET or RESET + * keywords). Can be converted to Value List using optionsDefListToRawValues + * and can be obtained from TEXT[] via optionsTextArrayToDefList functions. + * + * TEXT[] + * + * Options in form suitable for storig in TEXT[] field in DB. (E.g. reloptions + * are stores in pg_catalog.pg_class table in reloptions field). Can be + * converted to and from Value List using optionsValuesToTextArray and + * optionsTextArrayToRawValues functions. + * + * Bytea + * + * Option data stored in C-structure with varlena header in the beginning of + * the structure. This representation is used to pass option values to the core + * postgres. It is fast to read, it can be cached and so on. Bytea + * representation can be obtained from Value List using optionsValuesToBytea + * function, and can't be converted back. + */ + +/* + * OPTION STRING VALUE NOTION + * + * Important thing for bytea representation is that all data should be stored + * in one bytea chunk, including values of the string options. This is needed + * as * bytea representation is cached, and can freed, moved or recreated again + * without any notion, so it can't have parts allocated separately. + * + * Thus memory chunk for Bytea option values representation is divided into two + * parts. First goes a C-structure that stores fixed length values. Then goes + * memory area reserved for string values. + * + * For string values C-structure stores offsets (not pointers). These offsets + * can be used to access attached string value: + * + * String_pointer = Bytea_head_pointer + offset. + */ + +static option_spec_basic *allocateOptionSpec(int type, const char *name, + const char *desc, LOCKMODE lockmode, + int struct_offset, bool is_local, + option_value_postvalidate postvalidate_fn); + +static void parse_one_option(option_value *option, bool validate); +static void *optionsAllocateBytea(options_spec_set *spec_set, List *options); + + +static List *optionsDefListToRawValues(List *defList, bool is_for_reset); +static Datum optionsValuesToTextArray(List *options_values); +static List *optionsMergeOptionValues(List *old_options, List *new_options); + +/* + * allocateOptionsSpecSet + * Creates new Options Spec Set object: Allocates memory and initializes + * structure members. + * + * Spec Set items can be add via allocateOptionSpec and optionSpecSetAddItem + * functions or by calling directly any of optionsSpecSetAdd* function + * (preferable way) + * + * namespace - Spec Set can be bind to certain namespace (E.g. + * namespace.option=value). Options from other namespaces will be ignored while + * processing. If set to NULL, no namespace will be used at all. + * + * bytea_size - size of target structure of Bytea options representation + * + * num_items_expected - if you know expected number of Spec Set items set it + * here. Set to -1 in other cases. num_items_expected will be used for + * preallocating memory and will trigger error, if you try to add more items + * than you expected. + */ + +options_spec_set * +allocateOptionsSpecSet(const char *namspace, int bytea_size, bool is_local, + int num_items_expected) +{ + MemoryContext oldcxt; + options_spec_set *spec_set; + + if (!is_local) + oldcxt = MemoryContextSwitchTo(TopMemoryContext); + spec_set = palloc(sizeof(options_spec_set)); + if (namspace) + { + spec_set->namspace = palloc(strlen(namspace) + 1); + strcpy(spec_set->namspace, namspace); + } + else + spec_set->namspace = NULL; + if (num_items_expected > 0) + { + spec_set->num_allocated = num_items_expected; + spec_set->assert_on_realloc = true; + spec_set->definitions = palloc( + spec_set->num_allocated * sizeof(option_spec_basic *)); + } + else + { + spec_set->num_allocated = 0; + spec_set->assert_on_realloc = false; + spec_set->definitions = NULL; + } + spec_set->num = 0; + spec_set->struct_size = bytea_size; + spec_set->postprocess_fun = NULL; + spec_set->is_local = is_local; + if (!is_local) + MemoryContextSwitchTo(oldcxt); + return spec_set; +} + +/* + * allocateOptionSpec + * Allocates a new Option Specifiation object of desired type and + * initialize the type-independent fields + */ +static option_spec_basic * +allocateOptionSpec(int type, const char *name, const char *desc, + LOCKMODE lockmode, int struct_offset, bool is_local, + option_value_postvalidate postvalidate_fn) +{ + MemoryContext oldcxt; + size_t size; + option_spec_basic *newoption; + + if (!is_local) + oldcxt = MemoryContextSwitchTo(TopMemoryContext); + + switch (type) + { + case OPTION_TYPE_BOOL: + size = sizeof(option_spec_bool); + break; + case OPTION_TYPE_INT: + size = sizeof(option_spec_int); + break; + case OPTION_TYPE_REAL: + size = sizeof(option_spec_real); + break; + case OPTION_TYPE_ENUM: + size = sizeof(option_spec_enum); + break; + case OPTION_TYPE_STRING: + size = sizeof(option_spec_string); + break; + default: + elog(ERROR, "unsupported reloption type %d", (int) type); + return NULL; /* keep compiler quiet */ + } + + newoption = palloc(size); + + newoption->name = pstrdup(name); + if (desc) + newoption->desc = pstrdup(desc); + else + newoption->desc = NULL; + newoption->type = type; + newoption->lockmode = lockmode; + newoption->struct_offset = struct_offset; + newoption->postvalidate_fn = postvalidate_fn; + + if (!is_local) + MemoryContextSwitchTo(oldcxt); + + return newoption; +} + +/* + * optionSpecSetAddItem + * Adds pre-created Option Specification object to the Spec Set + */ +static void +optionSpecSetAddItem(option_spec_basic *newoption, + options_spec_set *spec_set) +{ + if (spec_set->num >= spec_set->num_allocated) + { + MemoryContext oldcxt = NULL; + + Assert(!spec_set->assert_on_realloc); + if (!spec_set->is_local) + oldcxt = MemoryContextSwitchTo(TopMemoryContext); + + if (spec_set->num_allocated == 0) + { + spec_set->num_allocated = 8; + spec_set->definitions = palloc( + spec_set->num_allocated * sizeof(option_spec_basic *)); + } + else + { + spec_set->num_allocated *= 2; + spec_set->definitions = repalloc(spec_set->definitions, + spec_set->num_allocated * sizeof(option_spec_basic *)); + } + if (!spec_set->is_local) + MemoryContextSwitchTo(oldcxt); + } + spec_set->definitions[spec_set->num] = newoption; + spec_set->num++; +} + + +/* + * optionsSpecSetAddBool + * Adds boolean Option Specification entry to the Spec Set + */ +void +optionsSpecSetAddBool(options_spec_set *spec_set, const char *name, + const char *desc, LOCKMODE lockmode, int struct_offset, + option_value_postvalidate postvalidate_fn, + bool default_val) +{ + option_spec_bool *spec_set_item; + + spec_set_item = (option_spec_bool *) allocateOptionSpec(OPTION_TYPE_BOOL, + name, desc, lockmode, struct_offset, + spec_set->is_local, postvalidate_fn); + + spec_set_item->default_val = default_val; + + optionSpecSetAddItem((option_spec_basic *) spec_set_item, spec_set); +} + +/* + * optionsSpecSetAddInt + * Adds integer Option Specification entry to the Spec Set + */ +void +optionsSpecSetAddInt(options_spec_set *spec_set, const char *name, + const char *desc, LOCKMODE lockmode, int struct_offset, + option_value_postvalidate postvalidate_fn, + int default_val, int min_val, int max_val) +{ + option_spec_int *spec_set_item; + + spec_set_item = (option_spec_int *) allocateOptionSpec(OPTION_TYPE_INT, + name, desc, lockmode, struct_offset, + spec_set->is_local, postvalidate_fn); + + spec_set_item->default_val = default_val; + spec_set_item->min = min_val; + spec_set_item->max = max_val; + + optionSpecSetAddItem((option_spec_basic *) spec_set_item, spec_set); +} + +/* + * optionsSpecSetAddReal + * Adds float Option Specification entry to the Spec Set + */ +void +optionsSpecSetAddReal(options_spec_set *spec_set, const char *name, + const char *desc, LOCKMODE lockmode, int struct_offset, + option_value_postvalidate postvalidate_fn, + double default_val, double min_val, double max_val) +{ + option_spec_real *spec_set_item; + + spec_set_item = (option_spec_real *) allocateOptionSpec(OPTION_TYPE_REAL, + name, desc, lockmode, struct_offset, + spec_set->is_local, postvalidate_fn); + + spec_set_item->default_val = default_val; + spec_set_item->min = min_val; + spec_set_item->max = max_val; + + optionSpecSetAddItem((option_spec_basic *) spec_set_item, spec_set); +} + +/* + * optionsSpecSetAddEnum + * Adds enum Option Specification entry to the Spec Set + * + * The members array must have a terminating NULL entry. + * + * The detailmsg is shown when unsupported values are passed, and has this + * form: "Valid values are \"foo\", \"bar\", and \"bar\"." + * + * The members array and detailmsg are not copied -- caller must ensure that + * they are valid throughout the life of the process. + */ + +void +optionsSpecSetAddEnum(options_spec_set *spec_set, const char *name, + const char *desc, LOCKMODE lockmode, int struct_offset, + option_value_postvalidate postvalidate_fn, + opt_enum_elt_def *members, int default_val, + const char *detailmsg) +{ + option_spec_enum *spec_set_item; + + spec_set_item = (option_spec_enum *) allocateOptionSpec(OPTION_TYPE_ENUM, + name, desc, lockmode, struct_offset, + spec_set->is_local, postvalidate_fn); + + spec_set_item->default_val = default_val; + spec_set_item->members = members; + spec_set_item->detailmsg = detailmsg; + + optionSpecSetAddItem((option_spec_basic *) spec_set_item, spec_set); +} + +/* + * optionsSpecSetAddString + * Adds string Option Specification entry to the Spec Set + * + * "validator" is an optional function pointer that can be used to test the + * validity of the values. It must elog(ERROR) when the argument string is + * not acceptable for the variable. Note that the default value must pass + * the validation. + */ +void +optionsSpecSetAddString(options_spec_set *spec_set, const char *name, + const char *desc, LOCKMODE lockmode, int struct_offset, + option_value_postvalidate postvalidate_fn, + const char *default_val, validate_string_option validator, + fill_string_option filler) +{ + option_spec_string *spec_set_item; + + /* make sure the validator/default combination is sane */ + if (validator) + (validator) (default_val); + + spec_set_item = (option_spec_string *) allocateOptionSpec( + OPTION_TYPE_STRING, + name, desc, lockmode, struct_offset, + spec_set->is_local, postvalidate_fn); + spec_set_item->validate_cb = validator; + spec_set_item->fill_cb = filler; + + if (default_val) + spec_set_item->default_val = MemoryContextStrdup(TopMemoryContext, + default_val); + else + spec_set_item->default_val = NULL; + optionSpecSetAddItem((option_spec_basic *) spec_set_item, spec_set); +} + +/* optionsDefListToRawValues + * Converts options values from DefList representation into Raw Values + * List. + * + * No parsing is done here except for checking that RESET syntax is correct + * (i.e. does not have =name part of value=name template). Syntax analyzer does + * not see difference between SET and RESET cases, so we should treat it here + * manually + */ +static List * +optionsDefListToRawValues(List *defList, bool is_for_reset) +{ + ListCell *cell; + List *result = NIL; + + foreach(cell, defList) + { + option_value *option_dst; + DefElem *def = (DefElem *) lfirst(cell); + char *value; + + option_dst = palloc(sizeof(option_value)); + + if (def->defnamespace) + { + option_dst->namspace = palloc(strlen(def->defnamespace) + 1); + strcpy(option_dst->namspace, def->defnamespace); + } + else + option_dst->namspace = NULL; + + option_dst->raw_name = palloc(strlen(def->defname) + 1); + strcpy(option_dst->raw_name, def->defname); + + if (is_for_reset) + { + /* + * If this option came from RESET statement we should throw error + * it it brings us name=value data, as syntax analyzer do not + * prevent it + */ + if (def->arg != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("RESET must not include values for parameters"))); + + option_dst->status = OPTION_VALUE_STATUS_FOR_RESET; + } + else + { + /* + * For SET statement we should treat (name) expression as if it is + * actually (name=true) so do it here manually. In other cases + * just use value as we should use it + */ + option_dst->status = OPTION_VALUE_STATUS_RAW; + if (def->arg != NULL) + value = defGetString(def); + else + value = "true"; + option_dst->raw_value = palloc(strlen(value) + 1); + strcpy(option_dst->raw_value, value); + } + + result = lappend(result, option_dst); + } + return result; +} + +/* + * optionsValuesToTextArray + * Converts options Values List (option_values) into TEXT[] representation + * + * This conversion is usually needed for saving option values into database + * (e.g. to store reloptions in pg_class.reloptions) + */ + +Datum +optionsValuesToTextArray(List *options_values) +{ + ArrayBuildState *astate = NULL; + ListCell *cell; + Datum result; + + foreach(cell, options_values) + { + option_value *option = (option_value *) lfirst(cell); + const char *name; + char *value; + text *t; + int len; + + /* + * Raw value were not cleared while parsing, so instead of converting + * it back, just use it to store value as text + */ + value = option->raw_value; + + Assert(option->status != OPTION_VALUE_STATUS_EMPTY); + + /* + * Name will be taken from option definition, if option were parsed or + * from raw_name if option were not parsed for some reason + */ + if (option->status == OPTION_VALUE_STATUS_PARSED) + name = option->gen->name; + else + name = option->raw_name; + + /* + * Now build "name=value" string and append it to the array + */ + len = VARHDRSZ + strlen(name) + strlen(value) + 1; + t = (text *) palloc(len + 1); + SET_VARSIZE(t, len); + sprintf(VARDATA(t), "%s=%s", name, value); + astate = accumArrayResult(astate, PointerGetDatum(t), false, + TEXTOID, CurrentMemoryContext); + } + if (astate) + result = makeArrayResult(astate, CurrentMemoryContext); + else + result = (Datum) 0; + + return result; +} + +/* + * optionsTextArrayToRawValues + * Converts option values from TEXT[] representation (datum_array) into + * Raw Values List. + * + * Used while fetching options values from DB, as a first step of converting + * them to other representations. + */ +List * +optionsTextArrayToRawValues(Datum array_datum) +{ + List *result = NIL; + + if (PointerIsValid(DatumGetPointer(array_datum))) + { + ArrayType *array = DatumGetArrayTypeP(array_datum); + Datum *options; + int noptions; + int i; + + deconstruct_array_builtin(array, TEXTOID, &options, NULL, &noptions); + + for (i = 0; i < noptions; i++) + { + option_value *option_dst; + char *text_str = VARDATA(options[i]); + int text_len = VARSIZE(options[i]) - VARHDRSZ; + int j; + int name_len = -1; + char *name; + int raw_value_len; + char *raw_value; + + /* + * Find position of '=' sign and treat id as a separator between + * name and value in "name=value" item + */ + for (j = 0; j < text_len; j = j + pg_mblen(text_str)) + { + if (text_str[j] == '=') + { + name_len = j; + break; + } + } + Assert(name_len >= 1); /* Just in case */ + + raw_value_len = text_len - name_len - 1; + + /* + * Copy name from src + */ + name = palloc(name_len + 1); + memcpy(name, text_str, name_len); + name[name_len] = '\0'; + + /* + * Copy value from src + */ + raw_value = palloc(raw_value_len + 1); + memcpy(raw_value, text_str + name_len + 1, raw_value_len); + raw_value[raw_value_len] = '\0'; + + /* + * Create new option_value item + */ + option_dst = palloc(sizeof(option_value)); + option_dst->status = OPTION_VALUE_STATUS_RAW; + option_dst->raw_name = name; + option_dst->raw_value = raw_value; + option_dst->namspace = NULL; + + result = lappend(result, option_dst); + } + } + return result; +} + +/* + * optionsMergeOptionValues + * Updates(or Resets) values from one Options Values List(old_options), + * with values from another Options Values List (new_options) + + * This function is used while ALTERing options of some object. + * If option from new_options list has OPTION_VALUE_STATUS_FOR_RESET flag + * on, option with that name will be excluded result list. + */ +static List * +optionsMergeOptionValues(List *old_options, List *new_options) +{ + List *result = NIL; + ListCell *old_cell; + ListCell *new_cell; + + /* + * First add to result all old options that are not mentioned in new list + */ + foreach(old_cell, old_options) + { + bool found; + const char *old_name; + option_value *old_option; + + old_option = (option_value *) lfirst(old_cell); + if (old_option->status == OPTION_VALUE_STATUS_PARSED) + old_name = old_option->gen->name; + else + old_name = old_option->raw_name; + + /* + * Looking for a new option with same name + */ + found = false; + foreach(new_cell, new_options) + { + option_value *new_option; + const char *new_name; + + new_option = (option_value *) lfirst(new_cell); + if (new_option->status == OPTION_VALUE_STATUS_PARSED) + new_name = new_option->gen->name; + else + new_name = new_option->raw_name; + + if (strcmp(new_name, old_name) == 0) + { + found = true; + break; + } + } + if (!found) + result = lappend(result, old_option); + } + + /* + * Now add all to result all new options that are not designated for reset + */ + foreach(new_cell, new_options) + { + option_value *new_option; + + new_option = (option_value *) lfirst(new_cell); + + if (new_option->status != OPTION_VALUE_STATUS_FOR_RESET) + result = lappend(result, new_option); + } + return result; +} + +/* + * optionsDefListValdateNamespaces + * Checks that defList has only options with namespaces from + * allowed_namspaces array. Items without namspace are also accepted + * + * Used while validation of syntax parser output. Error is thrown if unproper + * namespace is found. + */ +void +optionsDefListValdateNamespaces(List *defList, + const char * const* allowed_namspaces) +{ + ListCell *cell; + + foreach(cell, defList) + { + DefElem *def = (DefElem *) lfirst(cell); + + /* + * Checking namespace only for options that have namespaces. Options + * with no namespaces are always accepted + */ + if (def->defnamespace) + { + bool found = false; + int i = 0; + + while (allowed_namspaces[i]) + { + if (strcmp(def->defnamespace, + allowed_namspaces[i]) == 0) + { + found = true; + break; + } + i++; + } + if (!found) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter namespace \"%s\"", + def->defnamespace))); + } + } +} + +/* + * optionsDefListFilterNamespaces + * Filter out DefList items that has "namspace" namespace. If "namspace" + * is NULL, only namespaseless options are returned + */ +List * +optionsDefListFilterNamespaces(List *defList, const char *namspace) +{ + ListCell *cell; + List *result = NIL; + + foreach(cell, defList) + { + DefElem *def = (DefElem *) lfirst(cell); + + if ((!namspace && !def->defnamespace) || + (namspace && def->defnamespace && + strcmp(namspace, def->defnamespace) == 0)) + result = lappend(result, def); + } + return result; +} + +/* + * optionsTextArrayToDefList + * Converts option values from TEXT[] representation into DefList + * representation. + */ +List * +optionsTextArrayToDefList(Datum options) +{ + List *result = NIL; + ArrayType *array; + Datum *optiondatums; + int noptions; + int i; + + /* Nothing to do if no options */ + if (!PointerIsValid(DatumGetPointer(options))) + return result; + + array = DatumGetArrayTypeP(options); + + deconstruct_array_builtin(array, TEXTOID, &optiondatums, NULL, &noptions); + + for (i = 0; i < noptions; i++) + { + char *s; + char *p; + Node *val = NULL; + + s = TextDatumGetCString(optiondatums[i]); + p = strchr(s, '='); + if (p) + { + *p++ = '\0'; + val = (Node *) makeString(pstrdup(p)); + } + result = lappend(result, makeDefElem(pstrdup(s), val, -1)); + } + + return result; +} + +/* + * optionsDefListToTextArray + * Converts option values from DefList representation into TEXT[] + * representation. + */ +Datum +optionsDefListToTextArray(List *defList) +{ + ListCell *cell; + Datum result; + ArrayBuildState *astate = NULL; + + foreach(cell, defList) + { + DefElem *def = (DefElem *) lfirst(cell); + const char *name = def->defname; + const char *value; + text *t; + int len; + + if (def->arg != NULL) + value = defGetString(def); + else + value = "true"; + + if (def->defnamespace) + { + /* + * This function is used for backward compatibility in the place + * where namespases are not allowed + */ + Assert(false); /* Should not get here */ + return (Datum) 0; + } + len = VARHDRSZ + strlen(name) + strlen(value) + 1; + t = (text *) palloc(len + 1); + SET_VARSIZE(t, len); + sprintf(VARDATA(t), "%s=%s", name, value); + astate = accumArrayResult(astate, PointerGetDatum(t), false, + TEXTOID, CurrentMemoryContext); + + } + if (astate) + result = makeArrayResult(astate, CurrentMemoryContext); + else + result = (Datum) 0; + return result; +} + + +/* + * optionsParseRawValues + * Transforms RawValues List into [Parsed] Values List. Validation is done + * if validate flag is set. + * + * Options data that come parsed SQL query or DB storage, first converted into + * RawValues (where value are kept in text format), then is parsed into + * Values using this function + * + * Validation is used only for data that came from SQL query. We trust that + * data that came from DB is correct. + * + * If validation is off, all unknown options are kept unparsed so they will + * be stored back to DB until user RESETs them directly. + * + * This function destroys incoming list. + */ +List * +optionsParseRawValues(List *raw_values, options_spec_set *spec_set, + bool validate) +{ + ListCell *cell; + List *result = NIL; + bool *is_set; + int i; + + is_set = palloc0(sizeof(bool) * spec_set->num); + foreach(cell, raw_values) + { + option_value *option = (option_value *) lfirst(cell); + bool found = false; + + /* option values with RESET status does not need parsing */ + Assert(option->status != OPTION_VALUE_STATUS_FOR_RESET); + + /* Should not parse Values Set twice */ + Assert(option->status != OPTION_VALUE_STATUS_PARSED); + + if (validate && option->namspace && (!spec_set->namspace || + strcmp(spec_set->namspace, option->namspace) != 0)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter namespace \"%s\"", + option->namspace))); + + for (i = 0; i < spec_set->num; i++) + { + option_spec_basic *opt_spec = spec_set->definitions[i]; + + if (strcmp(option->raw_name, opt_spec->name) == 0) + { + if (validate && is_set[i]) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter \"%s\" specified more than once", + option->raw_name))); + + pfree(option->raw_name); + option->raw_name = NULL; + option->gen = opt_spec; + parse_one_option(option, validate); + is_set[i] = true; + found = true; + break; + } + } + if (validate && !found) + { + if (option->namspace) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter \"%s.%s\"", + option->namspace, option->raw_name))); + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter \"%s\"", + option->raw_name))); + } + result = lappend(result, option); + } + return result; +} + +/* + * parse_one_option + * Function to parse and validate single option. + * + * See optionsParseRawValues for more info. + * + * Link to Option Spec for the option is embedded into "option_value" + */ +static void +parse_one_option(option_value *option, bool validate) +{ + char *value; + bool parsed; + + value = option->raw_value; + + switch (option->gen->type) + { + case OPTION_TYPE_BOOL: + { + parsed = parse_bool(value, &option->values.bool_val); + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for boolean option \"%s\": %s", + option->gen->name, value))); + } + break; + case OPTION_TYPE_INT: + { + option_spec_int *optint = + (option_spec_int *) option->gen; + + parsed = parse_int(value, &option->values.int_val, 0, NULL); + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for integer option \"%s\": %s", + option->gen->name, value))); + if (validate && (option->values.int_val < optint->min || + option->values.int_val > optint->max)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("value %s out of bounds for option \"%s\"", + value, option->gen->name), + errdetail("Valid values are between \"%d\" and \"%d\".", + optint->min, optint->max))); + } + break; + case OPTION_TYPE_REAL: + { + option_spec_real *optreal = + (option_spec_real *) option->gen; + + parsed = parse_real(value, &option->values.real_val, 0, NULL); + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for floating point option \"%s\": %s", + option->gen->name, value))); + if (validate && (option->values.real_val < optreal->min || + option->values.real_val > optreal->max)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("value %s out of bounds for option \"%s\"", + value, option->gen->name), + errdetail("Valid values are between \"%f\" and \"%f\".", + optreal->min, optreal->max))); + } + break; + case OPTION_TYPE_ENUM: + { + option_spec_enum *optenum = + (option_spec_enum *) option->gen; + opt_enum_elt_def *elt; + + parsed = false; + for (elt = optenum->members; elt->string_val; elt++) + { + if (strcmp(value, elt->string_val) == 0) + { + option->values.enum_val = elt->symbol_val; + parsed = true; + break; + } + } + if (!parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for enum option \"%s\": %s", + option->gen->name, value), + optenum->detailmsg ? + errdetail_internal("%s", _(optenum->detailmsg)) : 0)); + } + break; + case OPTION_TYPE_STRING: + { + option_spec_string *optstring = + (option_spec_string *) option->gen; + + option->values.string_val = value; + if (validate && optstring->validate_cb) + (optstring->validate_cb) (value); + parsed = true; + } + break; + default: + elog(ERROR, "unsupported reloption type %d", (int) option->gen->type); + parsed = true; /* quiet compiler */ + break; + } + if (validate && option->gen->postvalidate_fn) + option->gen->postvalidate_fn(option); + + if (parsed) + option->status = OPTION_VALUE_STATUS_PARSED; + +} + +/* + * optionsAllocateBytea + * Allocates memory for Bytea options representation + * + * We need special function for this, as string option values are embedded into + * Bytea object, stored at the rear part of the memory chunk. Thus we need to + * allocate extra memory, so all string values would fit in. This function + * calculates required size and do allocation. + * + * See "OPTION STRING VALUE NOTION" at the beginning of the file for better + * understanding. + */ +static void * +optionsAllocateBytea(options_spec_set *spec_set, List *options) +{ + Size size; + int i; + ListCell *cell; + int length; + void *res; + + size = spec_set->struct_size; + + /* Calculate size needed to store all string values for this option */ + for (i = 0; i < spec_set->num; i++) + { + option_spec_basic *opt_spec = spec_set->definitions[i]; + option_spec_string *opt_spec_str; + bool found = false; + option_value *option; + const char *val = NULL; + + /* Not interested in non-string options, skipping */ + if (opt_spec->type != OPTION_TYPE_STRING) + continue; + + /* + * Trying to find option_value that references opt_spec entry + */ + opt_spec_str = (option_spec_string *) opt_spec; + foreach(cell, options) + { + option = (option_value *) lfirst(cell); + if (option->status == OPTION_VALUE_STATUS_PARSED && + strcmp(option->gen->name, opt_spec->name) == 0) + { + found = true; + break; + } + } + if (found) + val = option->values.string_val; + else + val = opt_spec_str->default_val; + + if (opt_spec_str->fill_cb) + length = opt_spec_str->fill_cb(val, NULL); + else if (val) + length = strlen(val) + 1; + else + length = 0; /* "Default Value is NULL" case */ + + /* Add total length of each string values to basic size */ + size += length; + } + + res = palloc0(size); + SET_VARSIZE(res, size); + return res; +} + +/* + * optionsValuesToBytea + * Converts options values from Value List representation to Bytea + * representation. + * + * Fills resulting Bytea with option values according to Option Sec Set. + * + * For understanding processing of the string option please read "OPTION STRING + * VALUE NOTION" at the beginning of the file. + */ +bytea * +optionsValuesToBytea(List *options, options_spec_set *spec_set) +{ + char *data; + char *string_values_buffer; + int i; + + data = optionsAllocateBytea(spec_set, options); + + /* place for string data starts right after original structure */ + string_values_buffer = data + spec_set->struct_size; + + for (i = 0; i < spec_set->num; i++) + { + option_value *found = NULL; + ListCell *cell; + char *item_pos; + option_spec_basic *opt_spec = spec_set->definitions[i]; + + if (opt_spec->struct_offset < 0) + continue; /* This option value should not be stored in + * Bytea for some reason. May be it is + * deprecated and has warning or error in + * postvalidate function */ + + /* Calculate the position of the item inside the structure */ + item_pos = data + opt_spec->struct_offset; + + /* Looking for the corresponding option from options list */ + foreach(cell, options) + { + option_value *option = (option_value *) lfirst(cell); + + if (option->status == OPTION_VALUE_STATUS_RAW) + continue; /* raw can come from db. Just ignore them then */ + Assert(option->status != OPTION_VALUE_STATUS_EMPTY); + + if (strcmp(opt_spec->name, option->gen->name) == 0) + { + found = option; + break; + } + } + /* writing to the proper position either option value or default val */ + switch (opt_spec->type) + { + case OPTION_TYPE_BOOL: + *(bool *) item_pos = found ? + found->values.bool_val : + ((option_spec_bool *) opt_spec)->default_val; + break; + case OPTION_TYPE_INT: + *(int *) item_pos = found ? + found->values.int_val : + ((option_spec_int *) opt_spec)->default_val; + break; + case OPTION_TYPE_REAL: + *(double *) item_pos = found ? + found->values.real_val : + ((option_spec_real *) opt_spec)->default_val; + break; + case OPTION_TYPE_ENUM: + *(int *) item_pos = found ? + found->values.enum_val : + ((option_spec_enum *) opt_spec)->default_val; + break; + + case OPTION_TYPE_STRING: + { + /* + * For string options: writing string value at the string + * buffer after the structure, and storing and offset to + * that value + */ + char *value = NULL; + option_spec_string *opt_spec_str = + (option_spec_string *) opt_spec; + + if (found) + value = found->values.string_val; + else + value = opt_spec_str->default_val; + + if (opt_spec_str->fill_cb) + { + Size size = + opt_spec_str->fill_cb(value, string_values_buffer); + + if (size) + { + *(int *) item_pos = string_values_buffer - data; + string_values_buffer += size; + } + else + *(int *) item_pos = + OPTION_STRING_VALUE_NOT_SET_OFFSET; + } + else + { + *(int *) item_pos = value ? + string_values_buffer - data : + OPTION_STRING_VALUE_NOT_SET_OFFSET; + if (value) + { + strcpy(string_values_buffer, value); + string_values_buffer += strlen(value) + 1; + } + } + } + break; + default: + elog(ERROR, "unsupported reloption type %d", + (int) opt_spec->type); + break; + } + } + return (void *) data; +} + +/* + * optionDefListToTextArray + * Converts options from defList to TEXT[] representation. + * + * Used when new relation (or other object) is created. defList comes from + * SQL syntax parser, TEXT[] goes to DB storage. Options are always validated + * while conversion. + */ +Datum +optionDefListToTextArray(options_spec_set *spec_set, List *defList) +{ + Datum result; + List *new_values; + + /* Parse and validate new values */ + new_values = optionsDefListToRawValues(defList, false); + new_values = optionsParseRawValues(new_values, spec_set, true); + + /* Some checks can be done in postprocess_fun, we should call it */ + if (spec_set->postprocess_fun) + { + bytea *data; + if (defList) + data = optionsValuesToBytea(new_values, spec_set); + else + data = NULL; + + spec_set->postprocess_fun(data, true); + if (data) pfree(data); + } + result = optionsValuesToTextArray(new_values); + return result; +} + +/* + * optionsUpdateTexArrayWithDefList + * Modifies oldOptions values (in TEXT[] representation) with defList + * values. + * + * Old values are appened or replaced with new values if do_reset flag is set + * to false. If do_reset is set to true, defList specify the list of the options + * that should be removed from original list. + */ + +Datum +optionsUpdateTexArrayWithDefList(options_spec_set *spec_set, Datum oldOptions, + List *defList, bool do_reset) +{ + Datum result; + List *new_values; + List *old_values; + List *merged_values; + + /* + * Parse and validate New values + */ + new_values = optionsDefListToRawValues(defList, do_reset); + if (!do_reset) + new_values = optionsParseRawValues(new_values, spec_set, true); + + if (PointerIsValid(DatumGetPointer(oldOptions))) + { + old_values = optionsTextArrayToRawValues(oldOptions); + merged_values = optionsMergeOptionValues(old_values, new_values); + } + else + { + if (do_reset) + merged_values = NULL; /* return nothing */ + else + merged_values = new_values; + } + + /* + * If we have postprocess_fun function defined in spec_set, then there + * might be some custom options checks there, with error throwing. So we + * should do it here to throw these errors while CREATing or ALTERing + * options + */ + if (spec_set->postprocess_fun) + { + bytea *data = optionsValuesToBytea(merged_values, spec_set); + + spec_set->postprocess_fun(data, true); + pfree(data); + } + + /* + * Convert options to TextArray format so caller can store them into + * database + */ + result = optionsValuesToTextArray(merged_values); + return result; +} + +/* + * optionsTextArrayToBytea + * Convert options values from TEXT[] representation into Bytea + * representation + * + * This function uses other conversion function to get desired result. + */ +bytea * +optionsTextArrayToBytea(options_spec_set *spec_set, Datum data, bool validate) +{ + List *values; + bytea *options; + + values = optionsTextArrayToRawValues(data); + values = optionsParseRawValues(values, spec_set, validate); + options = optionsValuesToBytea(values, spec_set); + + if (spec_set->postprocess_fun) + spec_set->postprocess_fun(options, false); + return options; +} diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 59fb53e7707..45e8eaaee25 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -1,7 +1,7 @@ /*------------------------------------------------------------------------- * * reloptions.c - * Core support for relation options (pg_class.reloptions) + * Support for relation options (pg_class.reloptions) * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California @@ -17,13 +17,10 @@ #include <float.h> -#include "access/gist_private.h" -#include "access/hash.h" #include "access/heaptoast.h" #include "access/htup_details.h" -#include "access/nbtree.h" #include "access/reloptions.h" -#include "access/spgist_private.h" +#include "access/options.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "commands/tablespace.h" @@ -34,6 +31,7 @@ #include "utils/guc.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "storage/bufmgr.h" /* * Contents of pg_class.reloptions @@ -91,408 +89,8 @@ * value has no effect until the next VACUUM, so no need for stronger lock. */ -static relopt_bool boolRelOpts[] = -{ - { - { - "autosummarize", - "Enables automatic summarization on this BRIN index", - RELOPT_KIND_BRIN, - AccessExclusiveLock - }, - false - }, - { - { - "autovacuum_enabled", - "Enables autovacuum in this relation", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, - { - { - "user_catalog_table", - "Declare a table as an additional catalog table, e.g. for the purpose of logical replication", - RELOPT_KIND_HEAP, - AccessExclusiveLock - }, - false - }, - { - { - "fastupdate", - "Enables \"fast update\" feature for this GIN index", - RELOPT_KIND_GIN, - AccessExclusiveLock - }, - true - }, - { - { - "security_barrier", - "View acts as a row security barrier", - RELOPT_KIND_VIEW, - AccessExclusiveLock - }, - false - }, - { - { - "security_invoker", - "Privileges on underlying relations are checked as the invoking user, not the view owner", - RELOPT_KIND_VIEW, - AccessExclusiveLock - }, - false - }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, - { - { - "deduplicate_items", - "Enables \"deduplicate items\" feature for this btree index", - RELOPT_KIND_BTREE, - ShareUpdateExclusiveLock /* since it applies only to later - * inserts */ - }, - true - }, - /* list terminator */ - {{NULL}} -}; - -static relopt_int intRelOpts[] = -{ - { - { - "fillfactor", - "Packs table pages only to this percentage", - RELOPT_KIND_HEAP, - ShareUpdateExclusiveLock /* since it applies only to later - * inserts */ - }, - HEAP_DEFAULT_FILLFACTOR, HEAP_MIN_FILLFACTOR, 100 - }, - { - { - "fillfactor", - "Packs btree index pages only to this percentage", - RELOPT_KIND_BTREE, - ShareUpdateExclusiveLock /* since it applies only to later - * inserts */ - }, - BTREE_DEFAULT_FILLFACTOR, BTREE_MIN_FILLFACTOR, 100 - }, - { - { - "fillfactor", - "Packs hash index pages only to this percentage", - RELOPT_KIND_HASH, - ShareUpdateExclusiveLock /* since it applies only to later - * inserts */ - }, - HASH_DEFAULT_FILLFACTOR, HASH_MIN_FILLFACTOR, 100 - }, - { - { - "fillfactor", - "Packs gist index pages only to this percentage", - RELOPT_KIND_GIST, - ShareUpdateExclusiveLock /* since it applies only to later - * inserts */ - }, - GIST_DEFAULT_FILLFACTOR, GIST_MIN_FILLFACTOR, 100 - }, - { - { - "fillfactor", - "Packs spgist index pages only to this percentage", - RELOPT_KIND_SPGIST, - ShareUpdateExclusiveLock /* since it applies only to later - * inserts */ - }, - SPGIST_DEFAULT_FILLFACTOR, SPGIST_MIN_FILLFACTOR, 100 - }, - { - { - "autovacuum_vacuum_threshold", - "Minimum number of tuple updates or deletes prior to vacuum", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, 0, INT_MAX - }, - { - { - "autovacuum_vacuum_max_threshold", - "Maximum number of tuple updates or deletes prior to vacuum", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -2, -1, INT_MAX - }, - { - { - "autovacuum_vacuum_insert_threshold", - "Minimum number of tuple inserts prior to vacuum, or -1 to disable insert vacuums", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -2, -1, INT_MAX - }, - { - { - "autovacuum_analyze_threshold", - "Minimum number of tuple inserts, updates or deletes prior to analyze", - RELOPT_KIND_HEAP, - ShareUpdateExclusiveLock - }, - -1, 0, INT_MAX - }, - { - { - "autovacuum_vacuum_cost_limit", - "Vacuum cost amount available before napping, for autovacuum", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, 1, 10000 - }, - { - { - "autovacuum_freeze_min_age", - "Minimum age at which VACUUM should freeze a table row, for autovacuum", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, 0, 1000000000 - }, - { - { - "autovacuum_multixact_freeze_min_age", - "Minimum multixact age at which VACUUM should freeze a row multixact's, for autovacuum", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, 0, 1000000000 - }, - { - { - "autovacuum_freeze_max_age", - "Age at which to autovacuum a table to prevent transaction ID wraparound", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, 100000, 2000000000 - }, - { - { - "autovacuum_multixact_freeze_max_age", - "Multixact age at which to autovacuum a table to prevent multixact wraparound", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, 10000, 2000000000 - }, - { - { - "autovacuum_freeze_table_age", - "Age at which VACUUM should perform a full table sweep to freeze row versions", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, -1, 0, 2000000000 - }, - { - { - "autovacuum_multixact_freeze_table_age", - "Age of multixact at which VACUUM should perform a full table sweep to freeze row versions", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, -1, 0, 2000000000 - }, - { - { - "log_autovacuum_min_duration", - "Sets the minimum execution time above which autovacuum actions will be logged", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, -1, INT_MAX - }, - { - { - "toast_tuple_target", - "Sets the target tuple length at which external columns will be toasted", - RELOPT_KIND_HEAP, - ShareUpdateExclusiveLock - }, - TOAST_TUPLE_TARGET, 128, TOAST_TUPLE_TARGET_MAIN - }, - { - { - "pages_per_range", - "Number of pages that each page range covers in a BRIN index", - RELOPT_KIND_BRIN, - AccessExclusiveLock - }, 128, 1, 131072 - }, - { - { - "gin_pending_list_limit", - "Maximum size of the pending list for this GIN index, in kilobytes.", - RELOPT_KIND_GIN, - AccessExclusiveLock - }, - -1, 64, MAX_KILOBYTES - }, - { - { - "effective_io_concurrency", - "Number of simultaneous requests that can be handled efficiently by the disk subsystem.", - RELOPT_KIND_TABLESPACE, - ShareUpdateExclusiveLock - }, -#ifdef USE_PREFETCH - -1, 0, MAX_IO_CONCURRENCY -#else - 0, 0, 0 -#endif - }, - { - { - "maintenance_io_concurrency", - "Number of simultaneous requests that can be handled efficiently by the disk subsystem for maintenance work.", - RELOPT_KIND_TABLESPACE, - ShareUpdateExclusiveLock - }, -#ifdef USE_PREFETCH - -1, 0, MAX_IO_CONCURRENCY -#else - 0, 0, 0 -#endif - }, - { - { - "parallel_workers", - "Number of parallel processes that can be used per executor node for this relation.", - RELOPT_KIND_HEAP, - ShareUpdateExclusiveLock - }, - -1, 0, 1024 - }, - - /* list terminator */ - {{NULL}} -}; - -static relopt_real realRelOpts[] = -{ - { - { - "autovacuum_vacuum_cost_delay", - "Vacuum cost delay in milliseconds, for autovacuum", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, 0.0, 100.0 - }, - { - { - "autovacuum_vacuum_scale_factor", - "Number of tuple updates or deletes prior to vacuum as a fraction of reltuples", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, 0.0, 100.0 - }, - { - { - "autovacuum_vacuum_insert_scale_factor", - "Number of tuple inserts prior to vacuum as a fraction of reltuples", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, 0.0, 100.0 - }, - { - { - "autovacuum_analyze_scale_factor", - "Number of tuple inserts, updates or deletes prior to analyze as a fraction of reltuples", - RELOPT_KIND_HEAP, - ShareUpdateExclusiveLock - }, - -1, 0.0, 100.0 - }, - { - { - "vacuum_max_eager_freeze_failure_rate", - "Fraction of pages in a relation vacuum can scan and fail to freeze before disabling eager scanning.", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, 0.0, 1.0 - }, - - { - { - "seq_page_cost", - "Sets the planner's estimate of the cost of a sequentially fetched disk page.", - RELOPT_KIND_TABLESPACE, - ShareUpdateExclusiveLock - }, - -1, 0.0, DBL_MAX - }, - { - { - "random_page_cost", - "Sets the planner's estimate of the cost of a nonsequentially fetched disk page.", - RELOPT_KIND_TABLESPACE, - ShareUpdateExclusiveLock - }, - -1, 0.0, DBL_MAX - }, - { - { - "n_distinct", - "Sets the planner's estimate of the number of distinct values appearing in a column (excluding child relations).", - RELOPT_KIND_ATTRIBUTE, - ShareUpdateExclusiveLock - }, - 0, -1.0, DBL_MAX - }, - { - { - "n_distinct_inherited", - "Sets the planner's estimate of the number of distinct values appearing in a column (including child relations).", - RELOPT_KIND_ATTRIBUTE, - ShareUpdateExclusiveLock - }, - 0, -1.0, DBL_MAX - }, - { - { - "vacuum_cleanup_index_scale_factor", - "Deprecated B-Tree parameter.", - RELOPT_KIND_BTREE, - ShareUpdateExclusiveLock - }, - -1, 0.0, 1e10 - }, - /* list terminator */ - {{NULL}} -}; - /* values from StdRdOptIndexCleanup */ -static relopt_enum_elt_def StdRdOptIndexCleanupValues[] = +static opt_enum_elt_def StdRdOptIndexCleanupValues[] = { {"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO}, {"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON}, @@ -506,17 +104,8 @@ static relopt_enum_elt_def StdRdOptIndexCleanupValues[] = {(const char *) NULL} /* list terminator */ }; -/* values from GistOptBufferingMode */ -static relopt_enum_elt_def gistBufferingOptValues[] = -{ - {"auto", GIST_OPTION_BUFFERING_AUTO}, - {"on", GIST_OPTION_BUFFERING_ON}, - {"off", GIST_OPTION_BUFFERING_OFF}, - {(const char *) NULL} /* list terminator */ -}; - /* values from ViewOptCheckOption */ -static relopt_enum_elt_def viewCheckOptValues[] = +static opt_enum_elt_def viewCheckOptValues[] = { /* no value for NOT_SET */ {"local", VIEW_OPTION_CHECK_OPTION_LOCAL}, @@ -524,225 +113,9 @@ static relopt_enum_elt_def viewCheckOptValues[] = {(const char *) NULL} /* list terminator */ }; -static relopt_enum enumRelOpts[] = -{ - { - { - "vacuum_index_cleanup", - "Controls index vacuuming and index cleanup", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - StdRdOptIndexCleanupValues, - STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO, - gettext_noop("Valid values are \"on\", \"off\", and \"auto\".") - }, - { - { - "buffering", - "Enables buffering build for this GiST index", - RELOPT_KIND_GIST, - AccessExclusiveLock - }, - gistBufferingOptValues, - GIST_OPTION_BUFFERING_AUTO, - gettext_noop("Valid values are \"on\", \"off\", and \"auto\".") - }, - { - { - "check_option", - "View has WITH CHECK OPTION defined (local or cascaded).", - RELOPT_KIND_VIEW, - AccessExclusiveLock - }, - viewCheckOptValues, - VIEW_OPTION_CHECK_OPTION_NOT_SET, - gettext_noop("Valid values are \"local\" and \"cascaded\".") - }, - /* list terminator */ - {{NULL}} -}; - -static relopt_string stringRelOpts[] = -{ - /* list terminator */ - {{NULL}} -}; - -static relopt_gen **relOpts = NULL; -static bits32 last_assigned_kind = RELOPT_KIND_LAST_DEFAULT; -static int num_custom_options = 0; -static relopt_gen **custom_options = NULL; -static bool need_initialization = true; - -static void initialize_reloptions(void); -static void parse_one_reloption(relopt_value *option, char *text_str, - int text_len, bool validate); - -/* - * Get the length of a string reloption (either default or the user-defined - * value). This is used for allocation purposes when building a set of - * relation options. - */ -#define GET_STRING_RELOPTION_LEN(option) \ - ((option).isset ? strlen((option).values.string_val) : \ - ((relopt_string *) (option).gen)->default_len) - -/* - * initialize_reloptions - * initialization routine, must be called before parsing - * - * Initialize the relOpts array and fill each variable's type and name length. - */ -static void -initialize_reloptions(void) -{ - int i; - int j; - - j = 0; - for (i = 0; boolRelOpts[i].gen.name; i++) - { - Assert(DoLockModesConflict(boolRelOpts[i].gen.lockmode, - boolRelOpts[i].gen.lockmode)); - j++; - } - for (i = 0; intRelOpts[i].gen.name; i++) - { - Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, - intRelOpts[i].gen.lockmode)); - j++; - } - for (i = 0; realRelOpts[i].gen.name; i++) - { - Assert(DoLockModesConflict(realRelOpts[i].gen.lockmode, - realRelOpts[i].gen.lockmode)); - j++; - } - for (i = 0; enumRelOpts[i].gen.name; i++) - { - Assert(DoLockModesConflict(enumRelOpts[i].gen.lockmode, - enumRelOpts[i].gen.lockmode)); - j++; - } - for (i = 0; stringRelOpts[i].gen.name; i++) - { - Assert(DoLockModesConflict(stringRelOpts[i].gen.lockmode, - stringRelOpts[i].gen.lockmode)); - j++; - } - j += num_custom_options; - - if (relOpts) - pfree(relOpts); - relOpts = MemoryContextAlloc(TopMemoryContext, - (j + 1) * sizeof(relopt_gen *)); - - j = 0; - for (i = 0; boolRelOpts[i].gen.name; i++) - { - relOpts[j] = &boolRelOpts[i].gen; - relOpts[j]->type = RELOPT_TYPE_BOOL; - relOpts[j]->namelen = strlen(relOpts[j]->name); - j++; - } - - for (i = 0; intRelOpts[i].gen.name; i++) - { - relOpts[j] = &intRelOpts[i].gen; - relOpts[j]->type = RELOPT_TYPE_INT; - relOpts[j]->namelen = strlen(relOpts[j]->name); - j++; - } - - for (i = 0; realRelOpts[i].gen.name; i++) - { - relOpts[j] = &realRelOpts[i].gen; - relOpts[j]->type = RELOPT_TYPE_REAL; - relOpts[j]->namelen = strlen(relOpts[j]->name); - j++; - } - - for (i = 0; enumRelOpts[i].gen.name; i++) - { - relOpts[j] = &enumRelOpts[i].gen; - relOpts[j]->type = RELOPT_TYPE_ENUM; - relOpts[j]->namelen = strlen(relOpts[j]->name); - j++; - } - - for (i = 0; stringRelOpts[i].gen.name; i++) - { - relOpts[j] = &stringRelOpts[i].gen; - relOpts[j]->type = RELOPT_TYPE_STRING; - relOpts[j]->namelen = strlen(relOpts[j]->name); - j++; - } - - for (i = 0; i < num_custom_options; i++) - { - relOpts[j] = custom_options[i]; - j++; - } - - /* add a list terminator */ - relOpts[j] = NULL; - - /* flag the work is complete */ - need_initialization = false; -} - -/* - * add_reloption_kind - * Create a new relopt_kind value, to be used in custom reloptions by - * user-defined AMs. - */ -relopt_kind -add_reloption_kind(void) -{ - /* don't hand out the last bit so that the enum's behavior is portable */ - if (last_assigned_kind >= RELOPT_KIND_MAX) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("user-defined relation parameter types limit exceeded"))); - last_assigned_kind <<= 1; - return (relopt_kind) last_assigned_kind; -} - -/* - * add_reloption - * Add an already-created custom reloption to the list, and recompute the - * main parser table. - */ -static void -add_reloption(relopt_gen *newoption) -{ - static int max_custom_options = 0; - - if (num_custom_options >= max_custom_options) - { - MemoryContext oldcxt; - - oldcxt = MemoryContextSwitchTo(TopMemoryContext); - - if (max_custom_options == 0) - { - max_custom_options = 8; - custom_options = palloc(max_custom_options * sizeof(relopt_gen *)); - } - else - { - max_custom_options *= 2; - custom_options = repalloc(custom_options, - max_custom_options * sizeof(relopt_gen *)); - } - MemoryContextSwitchTo(oldcxt); - } - custom_options[num_custom_options++] = newoption; - - need_initialization = true; -} +options_spec_set *get_stdrd_relopt_spec_set(bool is_heap); +void oid_postvalidate(option_value *value); /* * init_local_reloptions @@ -752,9 +125,8 @@ add_reloption(relopt_gen *newoption) void init_local_reloptions(local_relopts *relopts, Size relopt_struct_size) { - relopts->options = NIL; relopts->validators = NIL; - relopts->relopt_struct_size = relopt_struct_size; + relopts->spec_set = allocateOptionsSpecSet(NULL, relopt_struct_size, true, 0); } /* @@ -768,112 +140,6 @@ register_reloptions_validator(local_relopts *relopts, relopts_validator validato relopts->validators = lappend(relopts->validators, validator); } -/* - * add_local_reloption - * Add an already-created custom reloption to the local list. - */ -static void -add_local_reloption(local_relopts *relopts, relopt_gen *newoption, int offset) -{ - local_relopt *opt = palloc(sizeof(*opt)); - - Assert(offset < relopts->relopt_struct_size); - - opt->option = newoption; - opt->offset = offset; - - relopts->options = lappend(relopts->options, opt); -} - -/* - * allocate_reloption - * Allocate a new reloption and initialize the type-agnostic fields - * (for types other than string) - */ -static relopt_gen * -allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, - LOCKMODE lockmode) -{ - MemoryContext oldcxt; - size_t size; - relopt_gen *newoption; - - if (kinds != RELOPT_KIND_LOCAL) - oldcxt = MemoryContextSwitchTo(TopMemoryContext); - else - oldcxt = NULL; - - switch (type) - { - case RELOPT_TYPE_BOOL: - size = sizeof(relopt_bool); - break; - case RELOPT_TYPE_INT: - size = sizeof(relopt_int); - break; - case RELOPT_TYPE_REAL: - size = sizeof(relopt_real); - break; - case RELOPT_TYPE_ENUM: - size = sizeof(relopt_enum); - break; - case RELOPT_TYPE_STRING: - size = sizeof(relopt_string); - break; - default: - elog(ERROR, "unsupported reloption type %d", type); - return NULL; /* keep compiler quiet */ - } - - newoption = palloc(size); - - newoption->name = pstrdup(name); - if (desc) - newoption->desc = pstrdup(desc); - else - newoption->desc = NULL; - newoption->kinds = kinds; - newoption->namelen = strlen(name); - newoption->type = type; - newoption->lockmode = lockmode; - - if (oldcxt != NULL) - MemoryContextSwitchTo(oldcxt); - - return newoption; -} - -/* - * init_bool_reloption - * Allocate and initialize a new boolean reloption - */ -static relopt_bool * -init_bool_reloption(bits32 kinds, const char *name, const char *desc, - bool default_val, LOCKMODE lockmode) -{ - relopt_bool *newoption; - - newoption = (relopt_bool *) allocate_reloption(kinds, RELOPT_TYPE_BOOL, - name, desc, lockmode); - newoption->default_val = default_val; - - return newoption; -} - -/* - * add_bool_reloption - * Add a new boolean reloption - */ -void -add_bool_reloption(bits32 kinds, const char *name, const char *desc, - bool default_val, LOCKMODE lockmode) -{ - relopt_bool *newoption = init_bool_reloption(kinds, name, desc, - default_val, lockmode); - - add_reloption((relopt_gen *) newoption); -} - /* * add_local_bool_reloption * Add a new boolean local reloption @@ -884,47 +150,8 @@ void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset) { - relopt_bool *newoption = init_bool_reloption(RELOPT_KIND_LOCAL, - name, desc, - default_val, 0); - - add_local_reloption(relopts, (relopt_gen *) newoption, offset); -} - - -/* - * init_real_reloption - * Allocate and initialize a new integer reloption - */ -static relopt_int * -init_int_reloption(bits32 kinds, const char *name, const char *desc, - int default_val, int min_val, int max_val, - LOCKMODE lockmode) -{ - relopt_int *newoption; - - newoption = (relopt_int *) allocate_reloption(kinds, RELOPT_TYPE_INT, - name, desc, lockmode); - newoption->default_val = default_val; - newoption->min = min_val; - newoption->max = max_val; - - return newoption; -} - -/* - * add_int_reloption - * Add a new integer reloption - */ -void -add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, - int min_val, int max_val, LOCKMODE lockmode) -{ - relopt_int *newoption = init_int_reloption(kinds, name, desc, - default_val, min_val, - max_val, lockmode); - - add_reloption((relopt_gen *) newoption); + optionsSpecSetAddBool(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val); } /* @@ -938,47 +165,8 @@ add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset) { - relopt_int *newoption = init_int_reloption(RELOPT_KIND_LOCAL, - name, desc, default_val, - min_val, max_val, 0); - - add_local_reloption(relopts, (relopt_gen *) newoption, offset); -} - -/* - * init_real_reloption - * Allocate and initialize a new real reloption - */ -static relopt_real * -init_real_reloption(bits32 kinds, const char *name, const char *desc, - double default_val, double min_val, double max_val, - LOCKMODE lockmode) -{ - relopt_real *newoption; - - newoption = (relopt_real *) allocate_reloption(kinds, RELOPT_TYPE_REAL, - name, desc, lockmode); - newoption->default_val = default_val; - newoption->min = min_val; - newoption->max = max_val; - - return newoption; -} - -/* - * add_real_reloption - * Add a new float reloption - */ -void -add_real_reloption(bits32 kinds, const char *name, const char *desc, - double default_val, double min_val, double max_val, - LOCKMODE lockmode) -{ - relopt_real *newoption = init_real_reloption(kinds, name, desc, - default_val, min_val, - max_val, lockmode); - - add_reloption((relopt_gen *) newoption); + optionsSpecSetAddInt(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); } /* @@ -987,143 +175,29 @@ add_real_reloption(bits32 kinds, const char *name, const char *desc, * * 'offset' is offset of double-typed field. */ -void -add_local_real_reloption(local_relopts *relopts, const char *name, - const char *desc, double default_val, - double min_val, double max_val, int offset) -{ - relopt_real *newoption = init_real_reloption(RELOPT_KIND_LOCAL, - name, desc, - default_val, min_val, - max_val, 0); - - add_local_reloption(relopts, (relopt_gen *) newoption, offset); -} - -/* - * init_enum_reloption - * Allocate and initialize a new enum reloption - */ -static relopt_enum * -init_enum_reloption(bits32 kinds, const char *name, const char *desc, - relopt_enum_elt_def *members, int default_val, - const char *detailmsg, LOCKMODE lockmode) -{ - relopt_enum *newoption; - - newoption = (relopt_enum *) allocate_reloption(kinds, RELOPT_TYPE_ENUM, - name, desc, lockmode); - newoption->members = members; - newoption->default_val = default_val; - newoption->detailmsg = detailmsg; - - return newoption; -} - - -/* - * add_enum_reloption - * Add a new enum reloption - * - * The members array must have a terminating NULL entry. - * - * The detailmsg is shown when unsupported values are passed, and has this - * form: "Valid values are \"foo\", \"bar\", and \"bar\"." - * - * The members array and detailmsg are not copied -- caller must ensure that - * they are valid throughout the life of the process. - */ -void -add_enum_reloption(bits32 kinds, const char *name, const char *desc, - relopt_enum_elt_def *members, int default_val, - const char *detailmsg, LOCKMODE lockmode) -{ - relopt_enum *newoption = init_enum_reloption(kinds, name, desc, - members, default_val, - detailmsg, lockmode); - - add_reloption((relopt_gen *) newoption); -} - -/* - * add_local_enum_reloption - * Add a new local enum reloption - * - * 'offset' is offset of int-typed field. - */ -void -add_local_enum_reloption(local_relopts *relopts, const char *name, - const char *desc, relopt_enum_elt_def *members, - int default_val, const char *detailmsg, int offset) -{ - relopt_enum *newoption = init_enum_reloption(RELOPT_KIND_LOCAL, - name, desc, - members, default_val, - detailmsg, 0); - - add_local_reloption(relopts, (relopt_gen *) newoption, offset); -} - -/* - * init_string_reloption - * Allocate and initialize a new string reloption - */ -static relopt_string * -init_string_reloption(bits32 kinds, const char *name, const char *desc, - const char *default_val, - validate_string_relopt validator, - fill_string_relopt filler, - LOCKMODE lockmode) -{ - relopt_string *newoption; - - /* make sure the validator/default combination is sane */ - if (validator) - (validator) (default_val); - - newoption = (relopt_string *) allocate_reloption(kinds, RELOPT_TYPE_STRING, - name, desc, lockmode); - newoption->validate_cb = validator; - newoption->fill_cb = filler; - if (default_val) - { - if (kinds == RELOPT_KIND_LOCAL) - newoption->default_val = strdup(default_val); - else - newoption->default_val = MemoryContextStrdup(TopMemoryContext, default_val); - newoption->default_len = strlen(default_val); - newoption->default_isnull = false; - } - else - { - newoption->default_val = ""; - newoption->default_len = 0; - newoption->default_isnull = true; - } +void +add_local_real_reloption(local_relopts *relopts, const char *name, + const char *desc, double default_val, + double min_val, double max_val, int offset) +{ + optionsSpecSetAddReal(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); - return newoption; } /* - * add_string_reloption - * Add a new string reloption + * add_local_enum_reloption + * Add a new local enum reloption * - * "validator" is an optional function pointer that can be used to test the - * validity of the values. It must elog(ERROR) when the argument string is - * not acceptable for the variable. Note that the default value must pass - * the validation. + * 'offset' is offset of int-typed field. */ void -add_string_reloption(bits32 kinds, const char *name, const char *desc, - const char *default_val, validate_string_relopt validator, - LOCKMODE lockmode) +add_local_enum_reloption(local_relopts *relopts, const char *name, + const char *desc, opt_enum_elt_def *members, + int default_val, const char *detailmsg, int offset) { - relopt_string *newoption = init_string_reloption(kinds, name, desc, - default_val, - validator, NULL, - lockmode); - - add_reloption((relopt_gen *) newoption); + optionsSpecSetAddEnum(relopts->spec_set, name, desc, NoLock, offset, NULL, + members, default_val, detailmsg); } /* @@ -1139,247 +213,8 @@ add_local_string_reloption(local_relopts *relopts, const char *name, validate_string_relopt validator, fill_string_relopt filler, int offset) { - relopt_string *newoption = init_string_reloption(RELOPT_KIND_LOCAL, - name, desc, - default_val, - validator, filler, - 0); - - add_local_reloption(relopts, (relopt_gen *) newoption, offset); -} - -/* - * Transform a relation options list (list of DefElem) into the text array - * format that is kept in pg_class.reloptions, including only those options - * that are in the passed namespace. The output values do not include the - * namespace. - * - * This is used for three cases: CREATE TABLE/INDEX, ALTER TABLE SET, and - * ALTER TABLE RESET. In the ALTER cases, oldOptions is the existing - * reloptions value (possibly NULL), and we replace or remove entries - * as needed. - * - * If acceptOidsOff is true, then we allow oids = false, but throw error when - * on. This is solely needed for backwards compatibility. - * - * Note that this is not responsible for determining whether the options - * are valid, but it does check that namespaces for all the options given are - * listed in validnsps. The NULL namespace is always valid and need not be - * explicitly listed. Passing a NULL pointer means that only the NULL - * namespace is valid. - * - * Both oldOptions and the result are text arrays (or NULL for "default"), - * but we declare them as Datums to avoid including array.h in reloptions.h. - */ -Datum -transformRelOptions(Datum oldOptions, List *defList, const char *namspace, - const char *const validnsps[], bool acceptOidsOff, bool isReset) -{ - Datum result; - ArrayBuildState *astate; - ListCell *cell; - - /* no change if empty list */ - if (defList == NIL) - return oldOptions; - - /* We build new array using accumArrayResult */ - astate = NULL; - - /* Copy any oldOptions that aren't to be replaced */ - if (PointerIsValid(DatumGetPointer(oldOptions))) - { - ArrayType *array = DatumGetArrayTypeP(oldOptions); - Datum *oldoptions; - int noldoptions; - int i; - - deconstruct_array_builtin(array, TEXTOID, &oldoptions, NULL, &noldoptions); - - for (i = 0; i < noldoptions; i++) - { - char *text_str = VARDATA(oldoptions[i]); - int text_len = VARSIZE(oldoptions[i]) - VARHDRSZ; - - /* Search for a match in defList */ - foreach(cell, defList) - { - DefElem *def = (DefElem *) lfirst(cell); - int kw_len; - - /* ignore if not in the same namespace */ - if (namspace == NULL) - { - if (def->defnamespace != NULL) - continue; - } - else if (def->defnamespace == NULL) - continue; - else if (strcmp(def->defnamespace, namspace) != 0) - continue; - - kw_len = strlen(def->defname); - if (text_len > kw_len && text_str[kw_len] == '=' && - strncmp(text_str, def->defname, kw_len) == 0) - break; - } - if (!cell) - { - /* No match, so keep old option */ - astate = accumArrayResult(astate, oldoptions[i], - false, TEXTOID, - CurrentMemoryContext); - } - } - } - - /* - * If CREATE/SET, add new options to array; if RESET, just check that the - * user didn't say RESET (option=val). (Must do this because the grammar - * doesn't enforce it.) - */ - foreach(cell, defList) - { - DefElem *def = (DefElem *) lfirst(cell); - - if (isReset) - { - if (def->arg != NULL) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("RESET must not include values for parameters"))); - } - else - { - text *t; - const char *value; - Size len; - - /* - * Error out if the namespace is not valid. A NULL namespace is - * always valid. - */ - if (def->defnamespace != NULL) - { - bool valid = false; - int i; - - if (validnsps) - { - for (i = 0; validnsps[i]; i++) - { - if (strcmp(def->defnamespace, validnsps[i]) == 0) - { - valid = true; - break; - } - } - } - - if (!valid) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized parameter namespace \"%s\"", - def->defnamespace))); - } - - /* ignore if not in the same namespace */ - if (namspace == NULL) - { - if (def->defnamespace != NULL) - continue; - } - else if (def->defnamespace == NULL) - continue; - else if (strcmp(def->defnamespace, namspace) != 0) - continue; - - /* - * Flatten the DefElem into a text string like "name=arg". If we - * have just "name", assume "name=true" is meant. Note: the - * namespace is not output. - */ - if (def->arg != NULL) - value = defGetString(def); - else - value = "true"; - - /* - * This is not a great place for this test, but there's no other - * convenient place to filter the option out. As WITH (oids = - * false) will be removed someday, this seems like an acceptable - * amount of ugly. - */ - if (acceptOidsOff && def->defnamespace == NULL && - strcmp(def->defname, "oids") == 0) - { - if (defGetBoolean(def)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("tables declared WITH OIDS are not supported"))); - /* skip over option, reloptions machinery doesn't know it */ - continue; - } - - len = VARHDRSZ + strlen(def->defname) + 1 + strlen(value); - /* +1 leaves room for sprintf's trailing null */ - t = (text *) palloc(len + 1); - SET_VARSIZE(t, len); - sprintf(VARDATA(t), "%s=%s", def->defname, value); - - astate = accumArrayResult(astate, PointerGetDatum(t), - false, TEXTOID, - CurrentMemoryContext); - } - } - - if (astate) - result = makeArrayResult(astate, CurrentMemoryContext); - else - result = (Datum) 0; - - return result; -} - - -/* - * Convert the text-array format of reloptions into a List of DefElem. - * This is the inverse of transformRelOptions(). - */ -List * -untransformRelOptions(Datum options) -{ - List *result = NIL; - ArrayType *array; - Datum *optiondatums; - int noptions; - int i; - - /* Nothing to do if no options */ - if (!PointerIsValid(DatumGetPointer(options))) - return result; - - array = DatumGetArrayTypeP(options); - - deconstruct_array_builtin(array, TEXTOID, &optiondatums, NULL, &noptions); - - for (i = 0; i < noptions; i++) - { - char *s; - char *p; - Node *val = NULL; - - s = TextDatumGetCString(optiondatums[i]); - p = strchr(s, '='); - if (p) - { - *p++ = '\0'; - val = (Node *) makeString(p); - } - result = lappend(result, makeDefElem(s, val, -1)); - } - - return result; + optionsSpecSetAddString(relopts->spec_set, name, desc, NoLock, offset, + NULL, default_val, validator, filler); } /* @@ -1396,12 +231,13 @@ untransformRelOptions(Datum options) */ bytea * extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, - amoptions_function amoptions) + amreloptspecset_function amoptionsspecsetfn) { bytea *options; bool isnull; Datum datum; Form_pg_class classForm; + options_spec_set *spec_set; datum = fastgetattr(tuple, Anum_pg_class_reloptions, @@ -1415,548 +251,347 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, /* Parse into appropriate format; don't error out here */ switch (classForm->relkind) { - case RELKIND_RELATION: case RELKIND_TOASTVALUE: + spec_set = get_toast_relopt_spec_set(); + break; + case RELKIND_RELATION: case RELKIND_MATVIEW: - options = heap_reloptions(classForm->relkind, datum, false); + spec_set = get_heap_relopt_spec_set(); break; case RELKIND_PARTITIONED_TABLE: - options = partitioned_table_reloptions(datum, false); + spec_set = get_partitioned_relopt_spec_set(); break; case RELKIND_VIEW: - options = view_reloptions(datum, false); + spec_set = get_view_relopt_spec_set(); break; case RELKIND_INDEX: case RELKIND_PARTITIONED_INDEX: - options = index_reloptions(amoptions, datum, false); + if (amoptionsspecsetfn) + spec_set = amoptionsspecsetfn(); + else + spec_set = NULL; break; case RELKIND_FOREIGN_TABLE: - options = NULL; + spec_set = NULL; break; default: Assert(false); /* can't get here */ - options = NULL; /* keep compiler quiet */ + spec_set = NULL; /* keep compiler quiet */ break; } - + if (spec_set) + options = optionsTextArrayToBytea(spec_set, datum, 0); + else + options = NULL; return options; } -static void -parseRelOptionsInternal(Datum options, bool validate, - relopt_value *reloptions, int numoptions) +void +oid_postvalidate(option_value *value) { - ArrayType *array = DatumGetArrayTypeP(options); - Datum *optiondatums; - int noptions; - int i; - - deconstruct_array_builtin(array, TEXTOID, &optiondatums, NULL, &noptions); - - for (i = 0; i < noptions; i++) - { - char *text_str = VARDATA(optiondatums[i]); - int text_len = VARSIZE(optiondatums[i]) - VARHDRSZ; - int j; - - /* Search for a match in reloptions */ - for (j = 0; j < numoptions; j++) - { - int kw_len = reloptions[j].gen->namelen; - - if (text_len > kw_len && text_str[kw_len] == '=' && - strncmp(text_str, reloptions[j].gen->name, kw_len) == 0) - { - parse_one_reloption(&reloptions[j], text_str, text_len, - validate); - break; - } - } - - if (j >= numoptions && validate) - { - char *s; - char *p; - - s = TextDatumGetCString(optiondatums[i]); - p = strchr(s, '='); - if (p) - *p = '\0'; - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized parameter \"%s\"", s))); - } - } - - /* It's worth avoiding memory leaks in this function */ - pfree(optiondatums); - - if (((void *) array) != DatumGetPointer(options)) - pfree(array); + if (value->values.bool_val) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("tables declared WITH OIDS are not supported"))); } /* - * Interpret reloptions that are given in text-array format. + * Relation options and Lock levels: + * + * The default choice for any new option should be AccessExclusiveLock. + * In some cases the lock level can be reduced from there, but the lock + * level chosen should always conflict with itself to ensure that multiple + * changes aren't lost when we attempt concurrent changes. + * The choice of lock level depends completely upon how that parameter + * is used within the server, not upon how and when you'd like to change it. + * Safety first. Existing choices are documented here, and elsewhere in + * backend code where the parameters are used. + * + * In general, anything that affects the results obtained from a SELECT must be + * protected by AccessExclusiveLock. * - * options is a reloption text array as constructed by transformRelOptions. - * kind specifies the family of options to be processed. + * Autovacuum related parameters can be set at ShareUpdateExclusiveLock + * since they are only used by the AV procs and don't change anything + * currently executing. * - * The return value is a relopt_value * array on which the options actually - * set in the options array are marked with isset=true. The length of this - * array is returned in *numrelopts. Options not set are also present in the - * array; this is so that the caller can easily locate the default values. + * Fillfactor can be set because it applies only to subsequent changes made to + * data blocks, as documented in heapio.c * - * If there are no options of the given kind, numrelopts is set to 0 and NULL - * is returned (unless options are illegally supplied despite none being - * defined, in which case an error occurs). + * n_distinct options can be set at ShareUpdateExclusiveLock because they + * are only used during ANALYZE, which uses a ShareUpdateExclusiveLock, + * so the ANALYZE will not be affected by in-flight changes. Changing those + * values has no affect until the next ANALYZE, so no need for stronger lock. * - * Note: values of type int, bool and real are allocated as part of the - * returned array. Values of type string are allocated separately and must - * be freed by the caller. - */ -static relopt_value * -parseRelOptions(Datum options, bool validate, relopt_kind kind, - int *numrelopts) -{ - relopt_value *reloptions = NULL; - int numoptions = 0; - int i; - int j; - - if (need_initialization) - initialize_reloptions(); - - /* Build a list of expected options, based on kind */ - - for (i = 0; relOpts[i]; i++) - if (relOpts[i]->kinds & kind) - numoptions++; - - if (numoptions > 0) - { - reloptions = palloc(numoptions * sizeof(relopt_value)); - - for (i = 0, j = 0; relOpts[i]; i++) - { - if (relOpts[i]->kinds & kind) - { - reloptions[j].gen = relOpts[i]; - reloptions[j].isset = false; - j++; - } - } + * Planner-related parameters can be set with ShareUpdateExclusiveLock because + * they only affect planning and not the correctness of the execution. Plans + * cannot be changed in mid-flight, so changes here could not easily result in + * new improved plans in any case. So we allow existing queries to continue + * and existing plans to survive, a small price to pay for allowing better + * plans to be introduced concurrently without interfering with users. + * + * Setting parallel_workers is safe, since it acts the same as + * max_parallel_workers_per_gather which is a USERSET parameter that doesn't + * affect existing plans or queries. + */ + + +options_spec_set * +get_stdrd_relopt_spec_set(bool is_heap) +{ + options_spec_set *stdrd_relopt_spec_set = allocateOptionsSpecSet( + is_heap ? NULL : "toast", sizeof(StdRdOptions), false, 0); + + if (is_heap) + optionsSpecSetAddInt(stdrd_relopt_spec_set, "fillfactor", + "Packs table pages only to this percentag", + ShareUpdateExclusiveLock, /* since it applies only + * to later inserts */ + offsetof(StdRdOptions, fillfactor), NULL, + HEAP_DEFAULT_FILLFACTOR, HEAP_MIN_FILLFACTOR, 100); + + optionsSpecSetAddBool(stdrd_relopt_spec_set, "autovacuum_enabled", + "Enables autovacuum in this relation", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, enabled), + NULL, true); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_vacuum_threshold", + "Minimum number of tuple updates or deletes prior to vacuum", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, vacuum_threshold), + NULL, -1, 0, INT_MAX); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_vacuum_max_threshold", + "Maximum number of tuple updates or deletes prior to vacuum", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, vacuum_max_threshold), + NULL, -2, -1, INT_MAX); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_vacuum_insert_threshold", + "Minimum number of tuple inserts prior to vacuum, or -1 to disable insert vacuums", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, vacuum_ins_threshold), + NULL, -2, -1, INT_MAX); + + if (is_heap) + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_analyze_threshold", + "Minimum number of tuple updates or deletes prior to vacuum", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, analyze_threshold), + NULL, -1, 0, INT_MAX); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_vacuum_cost_limit", + "Vacuum cost amount available before napping, for autovacuum", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, vacuum_cost_limit), + NULL, -1, 0, 10000); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_freeze_min_age", + "Minimum age at which VACUUM should freeze a table row, for autovacuum", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, freeze_min_age), + NULL, -1, 0, 1000000000); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_freeze_max_age", + "Age at which to autovacuum a table to prevent transaction ID wraparound", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, freeze_max_age), + NULL, -1, 100000, 2000000000); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_freeze_table_age", + "Age at which VACUUM should perform a full table sweep to freeze row versions", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, freeze_table_age), + NULL, -1, 0, 2000000000); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_multixact_freeze_min_age", + "Minimum multixact age at which VACUUM should freeze a row multixact's, for autovacuum", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, multixact_freeze_min_age), + NULL, -1, 0, 1000000000); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_multixact_freeze_max_age", + "Multixact age at which to autovacuum a table to prevent multixact wraparound", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, multixact_freeze_max_age), + NULL, -1, 10000, 2000000000); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_multixact_freeze_table_age", + "Age of multixact at which VACUUM should perform a full table sweep to freeze row versions", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, multixact_freeze_table_age), + NULL, -1, 0, 2000000000); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "log_autovacuum_min_duration", + "Sets the minimum execution time above which autovacuum actions will be logged", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, log_min_duration), + NULL, -1, -1, INT_MAX); + + optionsSpecSetAddReal(stdrd_relopt_spec_set, "autovacuum_vacuum_cost_delay", + "Vacuum cost delay in milliseconds, for autovacuum", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, vacuum_cost_delay), + NULL, -1, 0.0, 100.0); + + optionsSpecSetAddReal(stdrd_relopt_spec_set, "autovacuum_vacuum_scale_factor", + "Number of tuple updates or deletes prior to vacuum as a fraction of reltuples", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, vacuum_scale_factor), + NULL, -1, 0.0, 100.0); + + optionsSpecSetAddReal(stdrd_relopt_spec_set, "autovacuum_vacuum_insert_scale_factor", + "Number of tuple inserts prior to vacuum as a fraction of reltuples", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, vacuum_ins_scale_factor), + NULL, -1, 0.0, 100.0); + if (is_heap) + { + optionsSpecSetAddReal(stdrd_relopt_spec_set, "autovacuum_analyze_scale_factor", + "Number of tuple inserts, updates or deletes prior to analyze as a fraction of reltuples", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, autovacuum) + + offsetof(AutoVacOpts, analyze_scale_factor), + NULL, -1, 0.0, 100.0); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "toast_tuple_target", + "Sets the target tuple length at which external columns will be toasted", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, toast_tuple_target), + NULL, TOAST_TUPLE_TARGET, 128, + TOAST_TUPLE_TARGET_MAIN); + + optionsSpecSetAddBool(stdrd_relopt_spec_set, "user_catalog_table", + "Declare a table as an additional catalog table, e.g. for the purpose of logical replication", + AccessExclusiveLock, + offsetof(StdRdOptions, user_catalog_table), + NULL, false); + + optionsSpecSetAddInt(stdrd_relopt_spec_set, "parallel_workers", + "Number of parallel processes that can be used per executor node for this relation.", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, parallel_workers), + NULL, -1, 0, 1024); } - /* Done if no options */ - if (PointerIsValid(DatumGetPointer(options))) - parseRelOptionsInternal(options, validate, reloptions, numoptions); + optionsSpecSetAddEnum(stdrd_relopt_spec_set, "vacuum_index_cleanup", + "Controls index vacuuming and index cleanup", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, vacuum_index_cleanup), + NULL, StdRdOptIndexCleanupValues, + STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO, + gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")); - *numrelopts = numoptions; - return reloptions; -} - -/* Parse local unregistered options. */ -static relopt_value * -parseLocalRelOptions(local_relopts *relopts, Datum options, bool validate) -{ - int nopts = list_length(relopts->options); - relopt_value *values = palloc(sizeof(*values) * nopts); - ListCell *lc; - int i = 0; + optionsSpecSetAddBool(stdrd_relopt_spec_set, "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, vacuum_truncate), + NULL, true); - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); - - values[i].gen = opt->option; - values[i].isset = false; - - i++; - } + optionsSpecSetAddReal(stdrd_relopt_spec_set, "vacuum_max_eager_freeze_failure_rate", + "Fraction of pages in a relation vacuum can scan and fail to freeze before disabling eager scanning", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, + vacuum_max_eager_freeze_failure_rate), + NULL, -1, 0.0, 1.0); - if (options != (Datum) 0) - parseRelOptionsInternal(options, validate, values, nopts); + if (is_heap) + optionsSpecSetAddBool(stdrd_relopt_spec_set, "oids", + "Backward compatibility option. Will do nothing when false, will throw error when true", + NoLock, + -1, /* Do not actually store it's value */ + &oid_postvalidate, false); - return values; + return stdrd_relopt_spec_set; } -/* - * Subroutine for parseRelOptions, to parse and validate a single option's - * value - */ -static void -parse_one_reloption(relopt_value *option, char *text_str, int text_len, - bool validate) -{ - char *value; - int value_len; - bool parsed; - bool nofree = false; - if (option->isset && validate) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("parameter \"%s\" specified more than once", - option->gen->name))); - - value_len = text_len - option->gen->namelen - 1; - value = (char *) palloc(value_len + 1); - memcpy(value, text_str + option->gen->namelen + 1, value_len); - value[value_len] = '\0'; - - switch (option->gen->type) - { - case RELOPT_TYPE_BOOL: - { - parsed = parse_bool(value, &option->values.bool_val); - if (validate && !parsed) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid value for boolean option \"%s\": %s", - option->gen->name, value))); - } - break; - case RELOPT_TYPE_INT: - { - relopt_int *optint = (relopt_int *) option->gen; - - parsed = parse_int(value, &option->values.int_val, 0, NULL); - if (validate && !parsed) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid value for integer option \"%s\": %s", - option->gen->name, value))); - if (validate && (option->values.int_val < optint->min || - option->values.int_val > optint->max)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("value %s out of bounds for option \"%s\"", - value, option->gen->name), - errdetail("Valid values are between \"%d\" and \"%d\".", - optint->min, optint->max))); - } - break; - case RELOPT_TYPE_REAL: - { - relopt_real *optreal = (relopt_real *) option->gen; - - parsed = parse_real(value, &option->values.real_val, 0, NULL); - if (validate && !parsed) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid value for floating point option \"%s\": %s", - option->gen->name, value))); - if (validate && (option->values.real_val < optreal->min || - option->values.real_val > optreal->max)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("value %s out of bounds for option \"%s\"", - value, option->gen->name), - errdetail("Valid values are between \"%f\" and \"%f\".", - optreal->min, optreal->max))); - } - break; - case RELOPT_TYPE_ENUM: - { - relopt_enum *optenum = (relopt_enum *) option->gen; - relopt_enum_elt_def *elt; - - parsed = false; - for (elt = optenum->members; elt->string_val; elt++) - { - if (pg_strcasecmp(value, elt->string_val) == 0) - { - option->values.enum_val = elt->symbol_val; - parsed = true; - break; - } - } - if (validate && !parsed) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid value for enum option \"%s\": %s", - option->gen->name, value), - optenum->detailmsg ? - errdetail_internal("%s", _(optenum->detailmsg)) : 0)); - - /* - * If value is not among the allowed string values, but we are - * not asked to validate, just use the default numeric value. - */ - if (!parsed) - option->values.enum_val = optenum->default_val; - } - break; - case RELOPT_TYPE_STRING: - { - relopt_string *optstring = (relopt_string *) option->gen; - - option->values.string_val = value; - nofree = true; - if (validate && optstring->validate_cb) - (optstring->validate_cb) (value); - parsed = true; - } - break; - default: - elog(ERROR, "unsupported reloption type %d", option->gen->type); - parsed = true; /* quiet compiler */ - break; - } +static options_spec_set *heap_relopt_spec_set = NULL; - if (parsed) - option->isset = true; - if (!nofree) - pfree(value); +options_spec_set * +get_heap_relopt_spec_set(void) +{ + if (heap_relopt_spec_set) + return heap_relopt_spec_set; + heap_relopt_spec_set = get_stdrd_relopt_spec_set(true); + return heap_relopt_spec_set; } /* - * Given the result from parseRelOptions, allocate a struct that's of the - * specified base size plus any extra space that's needed for string variables. - * - * "base" should be sizeof(struct) of the reloptions struct (StdRdOptions or - * equivalent). + * These toast options are can't be set via SQL, but we should set them + * to their defaults in binary representation, to make postgres work properly */ -static void * -allocateReloptStruct(Size base, relopt_value *options, int numoptions) +static void +toast_options_postprocess(void *data, bool validate) { - Size size = base; - int i; - - for (i = 0; i < numoptions; i++) + if (data) { - relopt_value *optval = &options[i]; + StdRdOptions *toast_options = (StdRdOptions *) data; - if (optval->gen->type == RELOPT_TYPE_STRING) - { - relopt_string *optstr = (relopt_string *) optval->gen; - - if (optstr->fill_cb) - { - const char *val = optval->isset ? optval->values.string_val : - optstr->default_isnull ? NULL : optstr->default_val; - - size += optstr->fill_cb(val, NULL); - } - else - size += GET_STRING_RELOPTION_LEN(*optval) + 1; - } + toast_options->fillfactor = 100; + toast_options->autovacuum.analyze_threshold = -1; + toast_options->autovacuum.analyze_scale_factor = -1; } - - return palloc0(size); } -/* - * Given the result of parseRelOptions and a parsing table, fill in the - * struct (previously allocated with allocateReloptStruct) with the parsed - * values. - * - * rdopts is the pointer to the allocated struct to be filled. - * basesize is the sizeof(struct) that was passed to allocateReloptStruct. - * options, of length numoptions, is parseRelOptions' output. - * elems, of length numelems, is the table describing the allowed options. - * When validate is true, it is expected that all options appear in elems. - */ -static void -fillRelOptions(void *rdopts, Size basesize, - relopt_value *options, int numoptions, - bool validate, - const relopt_parse_elt *elems, int numelems) +static options_spec_set *toast_relopt_spec_set = NULL; +options_spec_set * +get_toast_relopt_spec_set(void) { - int i; - int offset = basesize; + if (toast_relopt_spec_set) + return toast_relopt_spec_set; - for (i = 0; i < numoptions; i++) - { - int j; - bool found = false; + toast_relopt_spec_set = get_stdrd_relopt_spec_set(false); + toast_relopt_spec_set->postprocess_fun = toast_options_postprocess; - for (j = 0; j < numelems; j++) - { - if (strcmp(options[i].gen->name, elems[j].optname) == 0) - { - relopt_string *optstring; - char *itempos = ((char *) rdopts) + elems[j].offset; - char *string_val; - - switch (options[i].gen->type) - { - case RELOPT_TYPE_BOOL: - *(bool *) itempos = options[i].isset ? - options[i].values.bool_val : - ((relopt_bool *) options[i].gen)->default_val; - break; - case RELOPT_TYPE_INT: - *(int *) itempos = options[i].isset ? - options[i].values.int_val : - ((relopt_int *) options[i].gen)->default_val; - break; - case RELOPT_TYPE_REAL: - *(double *) itempos = options[i].isset ? - options[i].values.real_val : - ((relopt_real *) options[i].gen)->default_val; - break; - case RELOPT_TYPE_ENUM: - *(int *) itempos = options[i].isset ? - options[i].values.enum_val : - ((relopt_enum *) options[i].gen)->default_val; - break; - case RELOPT_TYPE_STRING: - optstring = (relopt_string *) options[i].gen; - if (options[i].isset) - string_val = options[i].values.string_val; - else if (!optstring->default_isnull) - string_val = optstring->default_val; - else - string_val = NULL; - - if (optstring->fill_cb) - { - Size size = - optstring->fill_cb(string_val, - (char *) rdopts + offset); - - if (size) - { - *(int *) itempos = offset; - offset += size; - } - else - *(int *) itempos = 0; - } - else if (string_val == NULL) - *(int *) itempos = 0; - else - { - strcpy((char *) rdopts + offset, string_val); - *(int *) itempos = offset; - offset += strlen(string_val) + 1; - } - break; - default: - elog(ERROR, "unsupported reloption type %d", - options[i].gen->type); - break; - } - found = true; - break; - } - } - if (validate && !found) - elog(ERROR, "reloption \"%s\" not found in parse table", - options[i].gen->name); - } - SET_VARSIZE(rdopts, offset); + return toast_relopt_spec_set; } /* - * Option parser for anything that uses StdRdOptions. + * Do not allow to set any option on partitioned table */ -bytea * -default_reloptions(Datum reloptions, bool validate, relopt_kind kind) +static void +partitioned_options_postprocess(void *data, bool validate) { - static const relopt_parse_elt tab[] = { - {"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)}, - {"autovacuum_enabled", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)}, - {"autovacuum_vacuum_threshold", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_threshold)}, - {"autovacuum_vacuum_max_threshold", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_max_threshold)}, - {"autovacuum_vacuum_insert_threshold", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_ins_threshold)}, - {"autovacuum_analyze_threshold", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_threshold)}, - {"autovacuum_vacuum_cost_limit", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_limit)}, - {"autovacuum_freeze_min_age", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_min_age)}, - {"autovacuum_freeze_max_age", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_max_age)}, - {"autovacuum_freeze_table_age", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_table_age)}, - {"autovacuum_multixact_freeze_min_age", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_min_age)}, - {"autovacuum_multixact_freeze_max_age", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_max_age)}, - {"autovacuum_multixact_freeze_table_age", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_table_age)}, - {"log_autovacuum_min_duration", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, log_min_duration)}, - {"toast_tuple_target", RELOPT_TYPE_INT, - offsetof(StdRdOptions, toast_tuple_target)}, - {"autovacuum_vacuum_cost_delay", RELOPT_TYPE_REAL, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_delay)}, - {"autovacuum_vacuum_scale_factor", RELOPT_TYPE_REAL, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_scale_factor)}, - {"autovacuum_vacuum_insert_scale_factor", RELOPT_TYPE_REAL, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_ins_scale_factor)}, - {"autovacuum_analyze_scale_factor", RELOPT_TYPE_REAL, - offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_scale_factor)}, - {"user_catalog_table", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, user_catalog_table)}, - {"parallel_workers", RELOPT_TYPE_INT, - offsetof(StdRdOptions, parallel_workers)}, - {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, - offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate)}, - {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, - offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} - }; - - return (bytea *) build_reloptions(reloptions, validate, kind, - sizeof(StdRdOptions), - tab, lengthof(tab)); + if (data && validate) + ereport(ERROR, + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot specify storage parameters for a partitioned table"), + errhint("Specify storage parameters for its leaf partitions instead.")); } -/* - * build_reloptions - * - * Parses "reloptions" provided by the caller, returning them in a - * structure containing the parsed options. The parsing is done with - * the help of a parsing table describing the allowed options, defined - * by "relopt_elems" of length "num_relopt_elems". - * - * "validate" must be true if reloptions value is freshly built by - * transformRelOptions(), as opposed to being read from the catalog, in which - * case the values contained in it must already be valid. - * - * NULL is returned if the passed-in options did not match any of the options - * in the parsing table, unless validate is true in which case an error would - * be reported. - */ -void * -build_reloptions(Datum reloptions, bool validate, - relopt_kind kind, - Size relopt_struct_size, - const relopt_parse_elt *relopt_elems, - int num_relopt_elems) -{ - int numoptions; - relopt_value *options; - void *rdopts; - - /* parse options specific to given relation option kind */ - options = parseRelOptions(reloptions, validate, kind, &numoptions); - Assert(numoptions <= num_relopt_elems); - /* if none set, we're done */ - if (numoptions == 0) - { - Assert(options == NULL); - return NULL; - } +static options_spec_set *partitioned_relopt_spec_set = NULL; - /* allocate and fill the structure */ - rdopts = allocateReloptStruct(relopt_struct_size, options, numoptions); - fillRelOptions(rdopts, relopt_struct_size, options, numoptions, - validate, relopt_elems, num_relopt_elems); +options_spec_set * +get_partitioned_relopt_spec_set(void) +{ + if (partitioned_relopt_spec_set) + return partitioned_relopt_spec_set; + partitioned_relopt_spec_set = get_stdrd_relopt_spec_set(true); - pfree(options); + /* No options for now, so Spec Set is empty */ + partitioned_relopt_spec_set->postprocess_fun = + partitioned_options_postprocess; - return rdopts; + return partitioned_relopt_spec_set; } /* @@ -1967,158 +602,165 @@ build_reloptions(Datum reloptions, bool validate, void * build_local_reloptions(local_relopts *relopts, Datum options, bool validate) { - int noptions = list_length(relopts->options); - relopt_parse_elt *elems = palloc(sizeof(*elems) * noptions); - relopt_value *vals; void *opts; - int i = 0; ListCell *lc; + List *values; + + values = optionsTextArrayToRawValues(options); + values = optionsParseRawValues(values, relopts->spec_set, validate); + opts = optionsValuesToBytea(values, relopts->spec_set); + + /* + * Kind of ugly conversion here for backward compatibility. Would be + * removed while moving opclass options to options.c API + */ - foreach(lc, relopts->options) + if (validate && relopts->validators) { - local_relopt *opt = lfirst(lc); + int val_count = list_length(values); + int i; + option_value *val_array; - elems[i].optname = opt->option->name; - elems[i].opttype = opt->option->type; - elems[i].offset = opt->offset; + val_array = palloc(sizeof(option_value) * val_count); - i++; - } + i = 0; + foreach(lc, values) + { + option_value *val = lfirst(lc); - vals = parseLocalRelOptions(relopts, options, validate); - opts = allocateReloptStruct(relopts->relopt_struct_size, vals, noptions); - fillRelOptions(opts, relopts->relopt_struct_size, vals, noptions, validate, - elems, noptions); + memcpy(&(val_array[i]), val, sizeof(option_value)); + i++; + } - if (validate) foreach(lc, relopts->validators) - ((relopts_validator) lfirst(lc)) (opts, vals, noptions); + ((relopts_validator) lfirst(lc)) (opts, val_array, val_count); - if (elems) - pfree(elems); + pfree(val_array); + } return opts; } /* - * Option parser for partitioned tables - */ -bytea * -partitioned_table_reloptions(Datum reloptions, bool validate) -{ - if (validate && reloptions) - ereport(ERROR, - errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot specify storage parameters for a partitioned table"), - errhint("Specify storage parameters for its leaf partitions instead.")); - return NULL; -} - -/* - * Option parser for views + * get_view_relopt_spec_set + * Returns an options catalog for view relation. */ -bytea * -view_reloptions(Datum reloptions, bool validate) -{ - static const relopt_parse_elt tab[] = { - {"security_barrier", RELOPT_TYPE_BOOL, - offsetof(ViewOptions, security_barrier)}, - {"security_invoker", RELOPT_TYPE_BOOL, - offsetof(ViewOptions, security_invoker)}, - {"check_option", RELOPT_TYPE_ENUM, - offsetof(ViewOptions, check_option)} - }; - - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_VIEW, - sizeof(ViewOptions), - tab, lengthof(tab)); -} +static options_spec_set *view_relopt_spec_set = NULL; -/* - * Parse options for heaps, views and toast tables. - */ -bytea * -heap_reloptions(char relkind, Datum reloptions, bool validate) +options_spec_set * +get_view_relopt_spec_set(void) { - StdRdOptions *rdopts; + if (view_relopt_spec_set) + return view_relopt_spec_set; - switch (relkind) - { - case RELKIND_TOASTVALUE: - rdopts = (StdRdOptions *) - default_reloptions(reloptions, validate, RELOPT_KIND_TOAST); - if (rdopts != NULL) - { - /* adjust default-only parameters for TOAST relations */ - rdopts->fillfactor = 100; - rdopts->autovacuum.analyze_threshold = -1; - rdopts->autovacuum.analyze_scale_factor = -1; - } - return (bytea *) rdopts; - case RELKIND_RELATION: - case RELKIND_MATVIEW: - return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP); - default: - /* other relkinds are not supported */ - return NULL; - } -} + view_relopt_spec_set = allocateOptionsSpecSet(NULL, + sizeof(ViewOptions), false, 3); + optionsSpecSetAddBool(view_relopt_spec_set, "security_barrier", + "View acts as a row security barrier", + AccessExclusiveLock, + offsetof(ViewOptions, security_barrier), NULL, false); -/* - * Parse options for indexes. - * - * amoptions index AM's option parser function - * reloptions options as text[] datum - * validate error flag - */ -bytea * -index_reloptions(amoptions_function amoptions, Datum reloptions, bool validate) -{ - Assert(amoptions != NULL); + optionsSpecSetAddBool(view_relopt_spec_set, "security_invoker", + "Privileges on underlying relations are checked as the invoking user, not the view owner", + AccessExclusiveLock, + offsetof(ViewOptions, security_invoker), NULL, false); - /* Assume function is strict */ - if (!PointerIsValid(DatumGetPointer(reloptions))) - return NULL; + optionsSpecSetAddEnum(view_relopt_spec_set, "check_option", + "View has WITH CHECK OPTION defined (local or cascaded)", + AccessExclusiveLock, + offsetof(ViewOptions, check_option), NULL, + viewCheckOptValues, + VIEW_OPTION_CHECK_OPTION_NOT_SET, + gettext_noop("Valid values are \"local\" and \"cascaded\".")); - return amoptions(reloptions, validate); + return view_relopt_spec_set; } /* - * Option parser for attribute reloptions + * get_attribute_options_spec_set + * Returns an Options Spec Set for heap attributes */ -bytea * -attribute_reloptions(Datum reloptions, bool validate) +static options_spec_set *attribute_options_spec_set = NULL; + +options_spec_set * +get_attribute_options_spec_set(void) { - static const relopt_parse_elt tab[] = { - {"n_distinct", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct)}, - {"n_distinct_inherited", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct_inherited)} - }; - - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_ATTRIBUTE, - sizeof(AttributeOpts), - tab, lengthof(tab)); + if (attribute_options_spec_set) + return attribute_options_spec_set; + + attribute_options_spec_set = allocateOptionsSpecSet(NULL, + sizeof(AttributeOpts), false, 2); + + optionsSpecSetAddReal(attribute_options_spec_set, "n_distinct", + "Sets the planner's estimate of the number of distinct values appearing in a column (excluding child relations).", + ShareUpdateExclusiveLock, + offsetof(AttributeOpts, n_distinct), NULL, + 0, -1.0, DBL_MAX); + + optionsSpecSetAddReal(attribute_options_spec_set, + "n_distinct_inherited", + "Sets the planner's estimate of the number of distinct values appearing in a column (including child relations).", + ShareUpdateExclusiveLock, + offsetof(AttributeOpts, n_distinct_inherited), NULL, + 0, -1.0, DBL_MAX); + + return attribute_options_spec_set; } + /* - * Option parser for tablespace reloptions - */ -bytea * -tablespace_reloptions(Datum reloptions, bool validate) + * get_tablespace_options_spec_set + * Returns an Options Spec Set for tablespaces +*/ +static options_spec_set *tablespace_options_spec_set = NULL; + +options_spec_set * +get_tablespace_options_spec_set(void) { - static const relopt_parse_elt tab[] = { - {"random_page_cost", RELOPT_TYPE_REAL, offsetof(TableSpaceOpts, random_page_cost)}, - {"seq_page_cost", RELOPT_TYPE_REAL, offsetof(TableSpaceOpts, seq_page_cost)}, - {"effective_io_concurrency", RELOPT_TYPE_INT, offsetof(TableSpaceOpts, effective_io_concurrency)}, - {"maintenance_io_concurrency", RELOPT_TYPE_INT, offsetof(TableSpaceOpts, maintenance_io_concurrency)} - }; - - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_TABLESPACE, - sizeof(TableSpaceOpts), - tab, lengthof(tab)); + if (tablespace_options_spec_set) + return tablespace_options_spec_set; + + tablespace_options_spec_set = allocateOptionsSpecSet(NULL, + sizeof(TableSpaceOpts), false, 4); + optionsSpecSetAddReal(tablespace_options_spec_set, + "random_page_cost", + "Sets the planner's estimate of the cost of a nonsequentially fetched disk page", + ShareUpdateExclusiveLock, + offsetof(TableSpaceOpts, random_page_cost), + NULL, -1, 0.0, DBL_MAX); + + optionsSpecSetAddReal(tablespace_options_spec_set, "seq_page_cost", + "Sets the planner's estimate of the cost of a sequentially fetched disk page", + ShareUpdateExclusiveLock, + offsetof(TableSpaceOpts, seq_page_cost), + NULL, -1, 0.0, DBL_MAX); + + optionsSpecSetAddInt(tablespace_options_spec_set, "effective_io_concurrency", + "Number of simultaneous requests that can be handled efficiently by the disk subsystem", + ShareUpdateExclusiveLock, + offsetof(TableSpaceOpts, effective_io_concurrency), + NULL, +#ifdef USE_PREFETCH + -1, 0, MAX_IO_CONCURRENCY +#else + 0, 0, 0 +#endif + ); + + optionsSpecSetAddInt(tablespace_options_spec_set, + "maintenance_io_concurrency", + "Number of simultaneous requests that can be handled efficiently by the disk subsystem for maintenance work.", + ShareUpdateExclusiveLock, + offsetof(TableSpaceOpts, maintenance_io_concurrency), + NULL, +#ifdef USE_PREFETCH + -1, 0, MAX_IO_CONCURRENCY +#else + 0, 0, 0 +#endif + ); + return tablespace_options_spec_set; } /* @@ -2128,33 +770,55 @@ tablespace_reloptions(Datum reloptions, bool validate) * for a longer explanation of how this works. */ LOCKMODE -AlterTableGetRelOptionsLockLevel(List *defList) +AlterTableGetRelOptionsLockLevel(Relation rel, List *defList) { LOCKMODE lockmode = NoLock; ListCell *cell; + options_spec_set *spec_set = NULL; if (defList == NIL) return AccessExclusiveLock; - if (need_initialization) - initialize_reloptions(); + switch (rel->rd_rel->relkind) + { + case RELKIND_TOASTVALUE: + spec_set = get_toast_relopt_spec_set(); + break; + case RELKIND_RELATION: + case RELKIND_MATVIEW: + spec_set = get_heap_relopt_spec_set(); + break; + case RELKIND_INDEX: + spec_set = rel->rd_indam->amreloptspecset(); + break; + case RELKIND_VIEW: + spec_set = get_view_relopt_spec_set(); + break; + case RELKIND_PARTITIONED_TABLE: + spec_set = get_partitioned_relopt_spec_set(); + break; + default: + Assert(false); /* can't get here */ + break; + } + Assert(spec_set); /* No spec set - no reloption change. Should + * never get here */ foreach(cell, defList) { DefElem *def = (DefElem *) lfirst(cell); + int i; - for (i = 0; relOpts[i]; i++) + for (i = 0; i < spec_set->num; i++) { - if (strncmp(relOpts[i]->name, - def->defname, - relOpts[i]->namelen + 1) == 0) - { - if (lockmode < relOpts[i]->lockmode) - lockmode = relOpts[i]->lockmode; - } + option_spec_basic *gen = spec_set->definitions[i]; + + if (pg_strcasecmp(gen->name, + def->defname) == 0) + if (lockmode < gen->lockmode) + lockmode = gen->lockmode; } } - return lockmode; } diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c index 1f9e58c4f1f..c27fed3cac5 100644 --- a/src/backend/access/gin/ginutil.c +++ b/src/backend/access/gin/ginutil.c @@ -16,7 +16,7 @@ #include "access/gin_private.h" #include "access/ginxlog.h" -#include "access/reloptions.h" +#include "access/options.h" #include "access/xloginsert.h" #include "catalog/pg_collation.h" #include "catalog/pg_type.h" @@ -27,6 +27,7 @@ #include "utils/index_selfuncs.h" #include "utils/rel.h" #include "utils/typcache.h" +#include "utils/guc.h" /* @@ -70,7 +71,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; amroutine->amgettreeheight = NULL; - amroutine->amoptions = ginoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = ginvalidate; @@ -85,6 +85,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gingetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -599,21 +600,6 @@ ginExtractEntries(GinState *ginstate, OffsetNumber attnum, return entries; } -bytea * -ginoptions(Datum reloptions, bool validate) -{ - static const relopt_parse_elt tab[] = { - {"fastupdate", RELOPT_TYPE_BOOL, offsetof(GinOptions, useFastUpdate)}, - {"gin_pending_list_limit", RELOPT_TYPE_INT, offsetof(GinOptions, - pendingListCleanupSize)} - }; - - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_GIN, - sizeof(GinOptions), - tab, lengthof(tab)); -} - /* * Fetch index's statistical data into *stats * @@ -700,3 +686,29 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build) END_CRIT_SECTION(); } + +static options_spec_set *gin_relopt_specset = NULL; + +options_spec_set * +gingetreloptspecset(void) +{ + if (gin_relopt_specset) + return gin_relopt_specset; + + gin_relopt_specset = allocateOptionsSpecSet(NULL, + sizeof(GinOptions), false, 2); + + optionsSpecSetAddBool(gin_relopt_specset, "fastupdate", + "Enables \"fast update\" feature for this GIN index", + AccessExclusiveLock, + offsetof(GinOptions, useFastUpdate), NULL, + GIN_DEFAULT_USE_FASTUPDATE); + + optionsSpecSetAddInt(gin_relopt_specset, "gin_pending_list_limit", + "Maximum size of the pending list for this GIN index, in kilobytes", + AccessExclusiveLock, + offsetof(GinOptions, pendingListCleanupSize), NULL, + -1, 64, MAX_KILOBYTES); + + return gin_relopt_specset; +} diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index 4d858b65e1e..b017ae6fcdd 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -92,7 +92,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; amroutine->amgettreeheight = NULL; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -109,6 +108,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amparallelrescan = NULL; amroutine->amtranslatestrategy = NULL; amroutine->amtranslatecmptype = gisttranslatecmptype; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index a6b701943d3..b578031207c 100644 --- a/src/backend/access/gist/gistutil.c +++ b/src/backend/access/gist/gistutil.c @@ -17,7 +17,7 @@ #include "access/gist_private.h" #include "access/htup_details.h" -#include "access/reloptions.h" +#include "access/options.h" #include "common/pg_prng.h" #include "storage/indexfsm.h" #include "utils/float.h" @@ -908,20 +908,6 @@ gistPageRecyclable(Page page) return false; } -bytea * -gistoptions(Datum reloptions, bool validate) -{ - static const relopt_parse_elt tab[] = { - {"fillfactor", RELOPT_TYPE_INT, offsetof(GiSTOptions, fillfactor)}, - {"buffering", RELOPT_TYPE_ENUM, offsetof(GiSTOptions, buffering_mode)} - }; - - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_GIST, - sizeof(GiSTOptions), - tab, lengthof(tab)); -} - /* * gistproperty() -- Check boolean properties of indexes. * @@ -1109,3 +1095,39 @@ gisttranslatecmptype(CompareType cmptype, Oid opfamily) result = OidFunctionCall1Coll(funcid, InvalidOid, Int32GetDatum(cmptype)); return DatumGetUInt16(result); } + +/* values from GistOptBufferingMode */ +static opt_enum_elt_def gistBufferingOptValues[] = +{ + {"auto", GIST_OPTION_BUFFERING_AUTO}, + {"on", GIST_OPTION_BUFFERING_ON}, + {"off", GIST_OPTION_BUFFERING_OFF}, + {(const char *) NULL} /* list terminator */ +}; + +static options_spec_set *gist_relopt_specset = NULL; + +options_spec_set * +gistgetreloptspecset(void) +{ + if (gist_relopt_specset) + return gist_relopt_specset; + + gist_relopt_specset = allocateOptionsSpecSet(NULL, + sizeof(GiSTOptions), false, 2); + + optionsSpecSetAddInt(gist_relopt_specset, "fillfactor", + "Packs gist index pages only to this percentage", + NoLock, /* No ALTER, no lock */ + offsetof(GiSTOptions, fillfactor), NULL, + GIST_DEFAULT_FILLFACTOR, GIST_MIN_FILLFACTOR, 100); + + optionsSpecSetAddEnum(gist_relopt_specset, "buffering", + "Enables buffering build for this GiST index", + NoLock, /* No ALTER, no lock */ + offsetof(GiSTOptions, buffering_mode), NULL, + gistBufferingOptValues, + GIST_OPTION_BUFFERING_AUTO, + gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")); + return gist_relopt_specset; +} diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index b24aae415ea..475c4f3fcee 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -91,7 +91,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; amroutine->amgettreeheight = NULL; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -108,6 +107,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amparallelrescan = NULL; amroutine->amtranslatestrategy = hashtranslatestrategy; amroutine->amtranslatecmptype = hashtranslatecmptype; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index 66c39f60654..8cc1e079200 100644 --- a/src/backend/access/hash/hashutil.c +++ b/src/backend/access/hash/hashutil.c @@ -15,7 +15,7 @@ #include "postgres.h" #include "access/hash.h" -#include "access/reloptions.h" +#include "access/options.h" #include "access/relscan.h" #include "port/pg_bitutils.h" #include "utils/lsyscache.h" @@ -271,19 +271,6 @@ _hash_checkpage(Relation rel, Buffer buf, int flags) } } -bytea * -hashoptions(Datum reloptions, bool validate) -{ - static const relopt_parse_elt tab[] = { - {"fillfactor", RELOPT_TYPE_INT, offsetof(HashOptions, fillfactor)}, - }; - - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_HASH, - sizeof(HashOptions), - tab, lengthof(tab)); -} - /* * _hash_get_indextuple_hashkey - get the hash index tuple's hash key value */ @@ -619,3 +606,22 @@ _hash_kill_items(IndexScanDesc scan) else _hash_relbuf(rel, buf); } + +static options_spec_set *hash_relopt_specset = NULL; + +options_spec_set * +hashgetreloptspecset(void) +{ + if (hash_relopt_specset) + return hash_relopt_specset; + + hash_relopt_specset = allocateOptionsSpecSet(NULL, + sizeof(HashOptions), false, 1); + optionsSpecSetAddInt(hash_relopt_specset, "fillfactor", + "Packs hash index pages only to this percentage", + NoLock, /* No ALTER -- no lock */ + offsetof(HashOptions, fillfactor), NULL, + HASH_DEFAULT_FILLFACTOR, HASH_MIN_FILLFACTOR, 100); + + return hash_relopt_specset; +} diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index 411d5ac0b5f..69a84a298e0 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -21,6 +21,7 @@ #include "access/nbtree.h" #include "access/relscan.h" #include "access/stratnum.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "nodes/execnodes.h" @@ -134,7 +135,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; amroutine->amgettreeheight = btgettreeheight; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -151,6 +151,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amparallelrescan = btparallelrescan; amroutine->amtranslatestrategy = bttranslatestrategy; amroutine->amtranslatecmptype = bttranslatecmptype; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1551,3 +1552,35 @@ bttranslatecmptype(CompareType cmptype, Oid opfamily) return InvalidStrategy; } } + +static options_spec_set *bt_relopt_specset = NULL; + +options_spec_set * +btgetreloptspecset(void) +{ + if (bt_relopt_specset) + return bt_relopt_specset; + + bt_relopt_specset = allocateOptionsSpecSet(NULL, + sizeof(BTOptions), false, 3); + + optionsSpecSetAddInt(bt_relopt_specset, "fillfactor", + "Packs btree index pages only to this percentage", + ShareUpdateExclusiveLock, /* affects inserts only */ + offsetof(BTOptions, fillfactor), NULL, + BTREE_DEFAULT_FILLFACTOR, BTREE_MIN_FILLFACTOR, 100); + + optionsSpecSetAddReal(bt_relopt_specset, "vacuum_cleanup_index_scale_factor", + "Number of tuple inserts prior to index cleanup as a fraction of reltuples", + ShareUpdateExclusiveLock, + offsetof(BTOptions, vacuum_cleanup_index_scale_factor), + NULL, -1, 0.0, 1e10); + + optionsSpecSetAddBool(bt_relopt_specset, "deduplicate_items", + "Enables \"deduplicate items\" feature for this btree index", + ShareUpdateExclusiveLock, /* affects inserts only */ + offsetof(BTOptions, deduplicate_items), NULL, + true); + + return bt_relopt_specset; +} diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c index 693e43c674b..cf24295c5cd 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,8 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" +#include "access/relscan.h" #include "commands/progress.h" #include "miscadmin.h" #include "utils/datum.h" @@ -2715,23 +2716,6 @@ BTreeShmemInit(void) Assert(found); } -bytea * -btoptions(Datum reloptions, bool validate) -{ - static const relopt_parse_elt tab[] = { - {"fillfactor", RELOPT_TYPE_INT, offsetof(BTOptions, fillfactor)}, - {"vacuum_cleanup_index_scale_factor", RELOPT_TYPE_REAL, - offsetof(BTOptions, vacuum_cleanup_index_scale_factor)}, - {"deduplicate_items", RELOPT_TYPE_BOOL, - offsetof(BTOptions, deduplicate_items)} - }; - - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_BTREE, - sizeof(BTOptions), - tab, lengthof(tab)); -} - /* * btproperty() -- Check boolean properties of indexes. * diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c index 367c36ef9af..c203c174fb9 100644 --- a/src/backend/access/spgist/spgutils.c +++ b/src/backend/access/spgist/spgutils.c @@ -17,7 +17,7 @@ #include "access/amvalidate.h" #include "access/htup_details.h" -#include "access/reloptions.h" +#include "access/options.h" #include "access/spgist_private.h" #include "access/toast_compression.h" #include "access/transam.h" @@ -77,7 +77,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; amroutine->amgettreeheight = NULL; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -94,6 +93,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amparallelrescan = NULL; amroutine->amtranslatestrategy = NULL; amroutine->amtranslatecmptype = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -749,22 +749,6 @@ SpGistInitMetapage(Page page) ((char *) metadata + sizeof(SpGistMetaPageData)) - (char *) page; } -/* - * reloptions processing for SPGiST - */ -bytea * -spgoptions(Datum reloptions, bool validate) -{ - static const relopt_parse_elt tab[] = { - {"fillfactor", RELOPT_TYPE_INT, offsetof(SpGistOptions, fillfactor)}, - }; - - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_SPGIST, - sizeof(SpGistOptions), - tab, lengthof(tab)); -} - /* * Get the space needed to store a non-null datum of the indicated type * in an inner tuple (that is, as a prefix or node label). @@ -1363,3 +1347,24 @@ spgproperty(Oid index_oid, int attno, return true; } + +static options_spec_set *spgist_relopt_specset = NULL; + +options_spec_set * +spggetreloptspecset(void) +{ + + if (spgist_relopt_specset) + return spgist_relopt_specset; + + spgist_relopt_specset = allocateOptionsSpecSet(NULL, + sizeof(SpGistOptions), false, 1); + + optionsSpecSetAddInt(spgist_relopt_specset, "fillfactor", + "Packs spgist index pages only to this percentage", + ShareUpdateExclusiveLock, /* affects only inserts */ + offsetof(SpGistOptions, fillfactor), NULL, + SPGIST_DEFAULT_FILLFACTOR, SPGIST_MIN_FILLFACTOR, 100); + + return spgist_relopt_specset; +} diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 44b4665ccd3..d59285b074e 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -85,6 +85,7 @@ create_ctas_internal(List *attrList, IntoClause *into) Datum toast_options; const char *const validnsps[] = HEAP_RELOPT_NAMESPACES; ObjectAddress intoRelationAddr; + List *toastDefList; /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview = (into->viewQuery != NULL); @@ -119,14 +120,12 @@ create_ctas_internal(List *attrList, IntoClause *into) CommandCounterIncrement(); /* parse and validate reloptions for the toast table */ - toast_options = transformRelOptions((Datum) 0, - create->options, - "toast", - validnsps, - true, false); - (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true); + optionsDefListValdateNamespaces(create->options, validnsps); + toastDefList = optionsDefListFilterNamespaces(create->options, "toast"); + toast_options = optionDefListToTextArray(get_toast_relopt_spec_set(), + toastDefList); NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options); /* Create the "view" part of a materialized view. */ diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c index c14e038d54f..1c6426e6c57 100644 --- a/src/backend/commands/foreigncmds.c +++ b/src/backend/commands/foreigncmds.c @@ -112,7 +112,7 @@ transformGenericOptions(Oid catalogId, List *options, Oid fdwvalidator) { - List *resultOptions = untransformRelOptions(oldOptions); + List *resultOptions = optionsTextArrayToDefList(oldOptions); ListCell *optcell; Datum result; diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index c92f5620ec1..b55b1cc0180 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -20,6 +20,7 @@ #include "access/heapam.h" #include "access/htup_details.h" #include "access/reloptions.h" +#include "access/options.h" #include "access/sysattr.h" #include "access/tableam.h" #include "access/xact.h" @@ -570,8 +571,8 @@ DefineIndex(Oid tableId, IndexAmRoutine *amRoutine; bool amcanorder; bool amissummarizing; - amoptions_function amoptions; bool exclusion; + amreloptspecset_function amreloptspecsetfn; bool partitioned; bool safe_index; Datum reloptions; @@ -893,7 +894,7 @@ DefineIndex(Oid tableId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; amissummarizing = amRoutine->amsummarizing; pfree(amRoutine); @@ -908,10 +909,18 @@ DefineIndex(Oid tableId, /* * Parse AM-specific options, convert to text array form, validate. */ - reloptions = transformRelOptions((Datum) 0, stmt->options, - NULL, NULL, false, false); - (void) index_reloptions(amoptions, reloptions, true); + if (stmt->options && !amreloptspecsetfn) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("access method %s does not support options", + accessMethodName))); + + if (amreloptspecsetfn) + reloptions = optionDefListToTextArray(amreloptspecsetfn(), + stmt->options); + else + reloptions = (Datum) 0; /* * Prepare arguments for index_create, primarily an IndexInfo structure. @@ -2246,8 +2255,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, Assert(attn < nkeycols); opclassOptions[attn] = - transformRelOptions((Datum) 0, attribute->opclassopts, - NULL, NULL, false, false); + optionsDefListToTextArray(attribute->opclassopts); } else opclassOptions[attn] = (Datum) 0; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index ce7d115667e..ae8f8961b7d 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -21,6 +21,7 @@ #include "access/heapam_xlog.h" #include "access/multixact.h" #include "access/reloptions.h" +#include "access/options.h" #include "access/relscan.h" #include "access/sysattr.h" #include "access/tableam.h" @@ -738,7 +739,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - const char *const validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -887,19 +887,36 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, /* * Parse and validate reloptions, if any. */ - reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps, - true, false); switch (relkind) { case RELKIND_VIEW: - (void) view_reloptions(reloptions, true); + reloptions = optionDefListToTextArray(get_view_relopt_spec_set(), + stmt->options); break; case RELKIND_PARTITIONED_TABLE: - (void) partitioned_table_reloptions(reloptions, true); - break; + { + /* If it is not all listed above, then it if heap */ + const char *const namespaces[] = HEAP_RELOPT_NAMESPACES; + List *heapDefList; + + optionsDefListValdateNamespaces(stmt->options, namespaces); + heapDefList = optionsDefListFilterNamespaces(stmt->options, NULL); + reloptions = optionDefListToTextArray( + get_partitioned_relopt_spec_set(), heapDefList); + break; + } default: - (void) heap_reloptions(relkind, reloptions, true); + { + /* If it is not all listed above, is should be heap */ + const char *const namespaces[] = HEAP_RELOPT_NAMESPACES; + List *heapDefList; + + optionsDefListValdateNamespaces(stmt->options, namespaces); + heapDefList = optionsDefListFilterNamespaces(stmt->options, NULL); + reloptions = optionDefListToTextArray(get_heap_relopt_spec_set(), + heapDefList); + } } if (stmt->ofTypename) @@ -4523,7 +4540,7 @@ void AlterTableInternal(Oid relid, List *cmds, bool recurse) { Relation rel; - LOCKMODE lockmode = AlterTableGetLockLevel(cmds); + LOCKMODE lockmode = AlterTableGetLockLevel(relid, cmds); rel = relation_open(relid, lockmode); @@ -4565,7 +4582,7 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse) * otherwise we might end up with an inconsistent dump that can't restore. */ LOCKMODE -AlterTableGetLockLevel(List *cmds) +AlterTableGetLockLevel(Oid relid, List *cmds) { /* * This only works if we read catalog tables using MVCC snapshots. @@ -4786,9 +4803,14 @@ AlterTableGetLockLevel(List *cmds) * getTables() */ case AT_ResetRelOptions: /* Uses MVCC in getIndexes() and * getTables() */ - cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def); - break; + { + Relation rel = relation_open(relid, AccessShareLock); + cmd_lockmode = AlterTableGetRelOptionsLockLevel(rel, + castNode(List, cmd->def)); + relation_close(rel, AccessShareLock); + break; + } case AT_AttachPartition: cmd_lockmode = ShareUpdateExclusiveLock; break; @@ -8916,12 +8938,13 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options, /* Generate new proposed attoptions (text array) */ datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions, &isnull); - newOptions = transformRelOptions(isnull ? (Datum) 0 : datum, - castNode(List, options), NULL, NULL, - false, isReset); - /* Validate new options */ - (void) attribute_reloptions(newOptions, true); + if (isnull) + datum = (Datum) 0; + newOptions = optionsUpdateTexArrayWithDefList( + get_attribute_options_spec_set(), + datum, castNode(List, options), + isReset); /* Build new tuple. */ memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); @@ -15702,12 +15725,13 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, HeapTuple tuple; HeapTuple newtuple; Datum datum; - bool isnull; Datum newOptions; Datum repl_val[Natts_pg_class]; bool repl_null[Natts_pg_class]; bool repl_repl[Natts_pg_class]; - const char *const validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + const char *const heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -15727,38 +15751,56 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, * there were none before. */ datum = (Datum) 0; - isnull = true; } else { + bool isnull; + /* Get the old reloptions */ datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isnull); + if (isnull) + datum = (Datum) 0; } /* Generate new proposed reloptions (text array) */ - newOptions = transformRelOptions(isnull ? (Datum) 0 : datum, - defList, NULL, validnsps, false, - operation == AT_ResetRelOptions); /* Validate */ + + optionsSpecSet = NULL; switch (rel->rd_rel->relkind) { case RELKIND_RELATION: - case RELKIND_TOASTVALUE: case RELKIND_MATVIEW: - (void) heap_reloptions(rel->rd_rel->relkind, newOptions, true); + optionsDefListValdateNamespaces(defList, heap_namespaces); + optionsDefList = optionsDefListFilterNamespaces(defList, NULL); + optionsSpecSet = get_heap_relopt_spec_set(); break; case RELKIND_PARTITIONED_TABLE: - (void) partitioned_table_reloptions(newOptions, true); + optionsDefListValdateNamespaces(defList, heap_namespaces); + optionsDefList = optionsDefListFilterNamespaces(defList, NULL); + optionsSpecSet = get_partitioned_relopt_spec_set(); break; case RELKIND_VIEW: - (void) view_reloptions(newOptions, true); + optionsDefList = defList; + optionsSpecSet = get_view_relopt_spec_set(); break; case RELKIND_INDEX: case RELKIND_PARTITIONED_INDEX: - (void) index_reloptions(rel->rd_indam->amoptions, newOptions, true); + if (!rel->rd_indam->amreloptspecset) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("index %s does not support options", + RelationGetRelationName(rel)))); + optionsDefList = defList; + optionsSpecSet = rel->rd_indam->amreloptspecset(); break; + case RELKIND_TOASTVALUE: + /* Should never get here */ + /* TOAST options are never altered directly */ + Assert(0); + /* FALLTHRU */ + /* If we get here in prod. error is the best option */ default: ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -15768,11 +15810,15 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, break; } + newOptions = optionsUpdateTexArrayWithDefList(optionsSpecSet, datum, + optionsDefList, + operation == AT_ResetRelOptions); + /* Special-case validation of view options */ if (rel->rd_rel->relkind == RELKIND_VIEW) { Query *view_query = get_view_query(rel); - List *view_options = untransformRelOptions(newOptions); + List *view_options = optionsTextArrayToDefList(newOptions); ListCell *cell; bool check_option = false; @@ -15848,20 +15894,23 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, * pretend there were none before. */ datum = (Datum) 0; - isnull = true; } else { + bool isnull; + /* Get the old reloptions */ datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isnull); + if (isnull) + datum = (Datum) 0; } - newOptions = transformRelOptions(isnull ? (Datum) 0 : datum, - defList, "toast", validnsps, false, - operation == AT_ResetRelOptions); - - (void) heap_reloptions(RELKIND_TOASTVALUE, newOptions, true); + optionsDefList = optionsDefListFilterNamespaces(defList, "toast"); + newOptions = optionsUpdateTexArrayWithDefList( + get_toast_relopt_spec_set(), + datum, optionsDefList, + operation == AT_ResetRelOptions); memset(repl_val, 0, sizeof(repl_val)); memset(repl_null, false, sizeof(repl_null)); diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c index a9005cc7212..5dc733905be 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -333,10 +333,9 @@ CreateTableSpace(CreateTableSpaceStmt *stmt) nulls[Anum_pg_tablespace_spcacl - 1] = true; /* Generate new proposed spcoptions (text array) */ - newOptions = transformRelOptions((Datum) 0, - stmt->options, - NULL, NULL, false, false); - (void) tablespace_reloptions(newOptions, true); + newOptions = optionDefListToTextArray(get_tablespace_options_spec_set(), + stmt->options); + if (newOptions != (Datum) 0) values[Anum_pg_tablespace_spcoptions - 1] = newOptions; else @@ -1052,11 +1051,12 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt) /* Generate new proposed spcoptions (text array) */ datum = heap_getattr(tup, Anum_pg_tablespace_spcoptions, RelationGetDescr(rel), &isnull); - newOptions = transformRelOptions(isnull ? (Datum) 0 : datum, - stmt->options, NULL, NULL, false, - stmt->isReset); - (void) tablespace_reloptions(newOptions, true); + if (isnull) + datum = (Datum) 0; + newOptions = optionsUpdateTexArrayWithDefList( + get_tablespace_options_spec_set(), + datum, stmt->options, stmt->isReset); /* Build new tuple. */ memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c index f0835fc3070..87780f32309 100644 --- a/src/backend/foreign/foreign.c +++ b/src/backend/foreign/foreign.c @@ -80,7 +80,7 @@ GetForeignDataWrapperExtended(Oid fdwid, bits16 flags) if (isnull) fdw->options = NIL; else - fdw->options = untransformRelOptions(datum); + fdw->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -167,7 +167,7 @@ GetForeignServerExtended(Oid serverid, bits16 flags) if (isnull) server->options = NIL; else - server->options = untransformRelOptions(datum); + server->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -239,7 +239,7 @@ GetUserMapping(Oid userid, Oid serverid) if (isnull) um->options = NIL; else - um->options = untransformRelOptions(datum); + um->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -276,7 +276,7 @@ GetForeignTable(Oid relid) if (isnull) ft->options = NIL; else - ft->options = untransformRelOptions(datum); + ft->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -309,7 +309,7 @@ GetForeignColumnOptions(Oid relid, AttrNumber attnum) if (isnull) options = NIL; else - options = untransformRelOptions(datum); + options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -526,7 +526,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -624,7 +624,7 @@ is_conninfo_option(const char *option, Oid context) Datum postgresql_fdw_validator(PG_FUNCTION_ARGS) { - List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); + List *options_list = optionsTextArrayToDefList(PG_GETARG_DATUM(0)); Oid catalog = PG_GETARG_OID(1); ListCell *cell; diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index abbe1bb45a3..51c3fffe3e8 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1925,7 +1925,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx, /* Add the operator class name, if non-default */ iparam->opclass = get_opclass(indclass->values[keyno], keycoltype); iparam->opclassopts = - untransformRelOptions(get_attoptions(source_relid, keyno + 1)); + optionsTextArrayToDefList(get_attoptions(source_relid, keyno + 1)); iparam->ordering = SORTBY_DEFAULT; iparam->nulls_ordering = SORTBY_NULLS_DEFAULT; @@ -1989,7 +1989,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx, datum = SysCacheGetAttr(RELOID, ht_idxrel, Anum_pg_class_reloptions, &isnull); if (!isnull) - index->options = untransformRelOptions(datum); + index->options = optionsTextArrayToDefList(datum); /* If it's a partial index, decompile and append the predicate */ datum = SysCacheGetAttr(INDEXRELID, ht_idx, diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 25fe3d58016..56cc463ee0e 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1159,6 +1159,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; const char *const validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1182,15 +1183,16 @@ ProcessUtilitySlow(ParseState *pstate, * parse and validate reloptions for the toast * table */ - toast_options = transformRelOptions((Datum) 0, - cstmt->options, - "toast", - validnsps, - true, - false); - (void) heap_reloptions(RELKIND_TOASTVALUE, - toast_options, - true); + + optionsDefListValdateNamespaces( + ((CreateStmt *) stmt)->options, + validnsps); + + toastDefList = optionsDefListFilterNamespaces( + ((CreateStmt *) stmt)->options, "toast"); + + toast_options = optionDefListToTextArray( + get_toast_relopt_spec_set(), toastDefList); NewRelationCreateToastTable(address.objectId, toast_options); @@ -1299,9 +1301,12 @@ ProcessUtilitySlow(ParseState *pstate, * lock on (for example) a relation on which we have no * permissions. */ - lockmode = AlterTableGetLockLevel(atstmt->cmds); - relid = AlterTableLookupRelation(atstmt, lockmode); - + relid = AlterTableLookupRelation(atstmt, AccessShareLock); + if (OidIsValid(relid)) + { + lockmode = AlterTableGetLockLevel(relid, atstmt->cmds); + relid = AlterTableLookupRelation(atstmt, lockmode); + } if (OidIsValid(relid)) { AlterTableUtilityContext atcontext; diff --git a/src/backend/utils/cache/attoptcache.c b/src/backend/utils/cache/attoptcache.c index 5c8360c08b5..f2850d5fc38 100644 --- a/src/backend/utils/cache/attoptcache.c +++ b/src/backend/utils/cache/attoptcache.c @@ -16,6 +16,7 @@ */ #include "postgres.h" +#include "access/options.h" #include "access/reloptions.h" #include "utils/attoptcache.h" #include "utils/catcache.h" @@ -176,7 +177,8 @@ get_attribute_options(Oid attrelid, int attnum) opts = NULL; else { - bytea *bytea_opts = attribute_reloptions(datum, false); + bytea *bytea_opts = optionsTextArrayToBytea( + get_attribute_options_spec_set(), datum, 0); opts = MemoryContextAlloc(CacheMemoryContext, VARSIZE(bytea_opts)); diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 398114373e9..6326c38d0df 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -465,7 +465,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -480,11 +480,11 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple) case RELKIND_VIEW: case RELKIND_MATVIEW: case RELKIND_PARTITIONED_TABLE: - amoptsfn = NULL; + amoptspecsetfn = NULL; break; case RELKIND_INDEX: case RELKIND_PARTITIONED_INDEX: - amoptsfn = relation->rd_indam->amoptions; + amoptspecsetfn = relation->rd_indam->amreloptspecset; break; default: return; @@ -495,7 +495,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple) * we might not have any other for pg_class yet (consider executing this * code for pg_class itself) */ - options = extractRelOptions(tuple, GetPgClassDescriptor(), amoptsfn); + options = extractRelOptions(tuple, GetPgClassDescriptor(), amoptspecsetfn); /* * Copy parsed data into CacheMemoryContext. To guard against the diff --git a/src/backend/utils/cache/spccache.c b/src/backend/utils/cache/spccache.c index 23458599298..3010692c442 100644 --- a/src/backend/utils/cache/spccache.c +++ b/src/backend/utils/cache/spccache.c @@ -149,7 +149,8 @@ get_tablespace(Oid spcid) opts = NULL; else { - bytea *bytea_opts = tablespace_reloptions(datum, false); + bytea *bytea_opts = optionsTextArrayToBytea( + get_tablespace_options_spec_set(), datum, 0); opts = MemoryContextAlloc(CacheMemoryContext, VARSIZE(bytea_opts)); memcpy(opts, bytea_opts, VARSIZE(bytea_opts)); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index fbe6b225ec9..c1a10ddf2d8 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -15,6 +15,7 @@ #include "access/cmptype.h" #include "access/genam.h" #include "access/stratnum.h" +#include "access/options.h" /* * We don't wish to include planner header files here, since most of an index @@ -159,9 +160,9 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, */ typedef int (*amgettreeheight_function) (Relation rel); -/* parse index reloptions */ -typedef bytea *(*amoptions_function) (Datum reloptions, - bool validate); + +/* get Spec Set for relation options */ +typedef options_spec_set *(*amreloptspecset_function) (); /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, @@ -292,7 +293,7 @@ typedef struct IndexAmRoutine amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; amgettreeheight_function amgettreeheight; /* can be NULL */ - amoptions_function amoptions; + amreloptspecset_function amreloptspecset; /* can be NULL */ amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; diff --git a/src/include/access/brin.h b/src/include/access/brin.h index 821f1e02806..88434a55612 100644 --- a/src/include/access/brin.h +++ b/src/include/access/brin.h @@ -37,6 +37,8 @@ typedef struct BrinStatsData #define BRIN_DEFAULT_PAGES_PER_RANGE 128 +#define BRIN_MIN_PAGES_PER_RANGE 1 +#define BRIN_MAX_PAGES_PER_RANGE 131072 #define BrinGetPagesPerRange(relation) \ (AssertMacro(relation->rd_rel->relkind == RELKIND_INDEX && \ relation->rd_rel->relam == BRIN_AM_OID), \ diff --git a/src/include/access/brin_internal.h b/src/include/access/brin_internal.h index d093a0bf130..2513250fe6a 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -109,6 +109,7 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bytea *brinoptions(Datum reloptions, bool validate); +extern options_spec_set *bringetreloptspecset(void); /* brin_validate.c */ extern bool brinvalidate(Oid opclassoid); diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h index 50478db9820..271482e427a 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -109,6 +109,7 @@ extern Datum *ginExtractEntries(GinState *ginstate, OffsetNumber attnum, extern OffsetNumber gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple); extern Datum gintuple_get_key(GinState *ginstate, IndexTuple tuple, GinNullCategory *category); +extern options_spec_set *gingetreloptspecset(void); /* gininsert.c */ extern IndexBuildResult *ginbuild(Relation heap, Relation index, diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h index 39404ec7cdb..7ed2230e0be 100644 --- a/src/include/access/gist_private.h +++ b/src/include/access/gist_private.h @@ -22,6 +22,7 @@ #include "storage/buffile.h" #include "utils/hsearch.h" #include "access/genam.h" +#include "access/options.h" /* * Maximum number of "halves" a page can be split into in one operation. @@ -388,6 +389,7 @@ typedef enum GistOptBufferingMode GIST_OPTION_BUFFERING_OFF, } GistOptBufferingMode; + /* * Storage type for GiST's reloptions */ @@ -479,7 +481,7 @@ extern void gistadjustmembers(Oid opfamilyoid, #define GIST_MIN_FILLFACTOR 10 #define GIST_DEFAULT_FILLFACTOR 90 -extern bytea *gistoptions(Datum reloptions, bool validate); +extern options_spec_set *gistgetreloptspecset(void); extern bool gistproperty(Oid index_oid, int attno, IndexAMProperty prop, const char *propname, bool *res, bool *isnull); diff --git a/src/include/access/hash.h b/src/include/access/hash.h index 073ad29b19b..3dccf6ed279 100644 --- a/src/include/access/hash.h +++ b/src/include/access/hash.h @@ -380,7 +380,6 @@ extern IndexBulkDeleteResult *hashbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *hashvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *hashoptions(Datum reloptions, bool validate); extern bool hashvalidate(Oid opclassoid); extern void hashadjustmembers(Oid opfamilyoid, Oid opclassoid, @@ -477,6 +476,7 @@ extern BlockNumber _hash_get_newblock_from_oldbucket(Relation rel, Bucket old_bu extern Bucket _hash_get_newbucket_from_oldbucket(Relation rel, Bucket old_bucket, uint32 lowmask, uint32 maxbucket); extern void _hash_kill_items(IndexScanDesc scan); +extern options_spec_set *hashgetreloptspecset(void); /* hash.c */ extern void hashbucketcleanup(Relation rel, Bucket cur_bucket, diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h index e4fdeca3402..d86f150e50e 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1307,7 +1307,7 @@ extern void _bt_end_vacuum(Relation rel); extern void _bt_end_vacuum_callback(int code, Datum arg); extern Size BTreeShmemSize(void); extern void BTreeShmemInit(void); -extern bytea *btoptions(Datum reloptions, bool validate); +extern options_spec_set *btgetreloptspecset(void); extern bool btproperty(Oid index_oid, int attno, IndexAMProperty prop, const char *propname, bool *res, bool *isnull); diff --git a/src/include/access/options.h b/src/include/access/options.h new file mode 100644 index 00000000000..c1df04c90f4 --- /dev/null +++ b/src/include/access/options.h @@ -0,0 +1,274 @@ +/*------------------------------------------------------------------------- + * + * options.h + * An uniform, context-free API for processing name=value options. Used + * to process relation options (reloptions), attribute options, opclass + * options, etc. + * + * Note: the functions dealing with text-array options values declare + * them as Datum, not ArrayType *, to avoid needing to include array.h + * into a lot of low-level code. + * + * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * src/include/access/options.h + * + *------------------------------------------------------------------------- + */ + +#ifndef OPTIONS_H +#define OPTIONS_H + +#include "storage/lock.h" +#include "nodes/pg_list.h" + + +/* supported option types */ +typedef enum option_type +{ + OPTION_TYPE_BOOL, + OPTION_TYPE_INT, + OPTION_TYPE_REAL, + OPTION_TYPE_ENUM, + OPTION_TYPE_STRING, +} option_type; + +/* + * Each Option Value item passes through certain life cycle. Option Value + * Status specifies at what point of life cycle this Option Value is now. + * Option Value Statuses are: + * EMPTY - Option Value structure have been just created + * RAW - Option Value have been read, but has not been parsed or validated yet. + * Use raw_value structure member to access Raw Value + * PARSED - Option Value have been parsed, you should use proper union member + * to access Parsed Option Value, according to Option Type + * FOR_RESET - Specifies that this Option is designated for resetting to + * default value + */ +typedef enum option_value_status +{ + OPTION_VALUE_STATUS_EMPTY, /* Option was just initialized */ + OPTION_VALUE_STATUS_RAW, /* Option just came from syntax analyzer in + * has name, and raw (unparsed) value */ + OPTION_VALUE_STATUS_PARSED, /* Option was parsed and has link to Option + * Spec Set entry and proper value */ + OPTION_VALUE_STATUS_FOR_RESET, /* This option came from ALTER xxx RESET */ +} option_value_status; + +/* + * opt_enum_elt_def -- One member of the array of acceptable values + * of an enum reloption. + */ +typedef struct opt_enum_elt_def +{ + const char *string_val; + int symbol_val; +} opt_enum_elt_def; + + +typedef struct option_value option_value; + +/* Function that would be called after option validation. + * Might be needed for custom warnings, errors, or for changing + * option value after being validated, etc. + */ +typedef void (*option_value_postvalidate) (option_value *value); + +/* generic structure to store Option Spec information */ +typedef struct option_spec_basic +{ + const char *name; /* must be first (used as list termination + * marker) */ + const char *desc; + LOCKMODE lockmode; + option_type type; + int struct_offset; /* offset of the value in Bytea representation */ + option_value_postvalidate postvalidate_fn; +} option_spec_basic; + + +/* reloptions records for specific variable types */ +typedef struct option_spec_bool +{ + option_spec_basic base; + bool default_val; +} option_spec_bool; + +typedef struct option_spec_int +{ + option_spec_basic base; + int default_val; + int min; + int max; +} option_spec_int; + +typedef struct option_spec_real +{ + option_spec_basic base; + double default_val; + double min; + double max; +} option_spec_real; + +typedef struct option_spec_enum +{ + option_spec_basic base; + opt_enum_elt_def *members; /* Null terminated array of allowed names and + * corresponding values */ + int default_val; /* Default value, may differ from values in + * members array */ + const char *detailmsg; +} option_spec_enum; + +/* validation routines for strings */ +typedef void (*validate_string_option) (const char *value); +typedef Size (*fill_string_option) (const char *value, void *ptr); + +/* + * When storing sting reloptions, we should deal with special case when + * option value is not set. For fixed length options, we just copy default + * option value into the binary structure. For varlen value, there can be + * "not set" special case, with no default value offered. + * In this case we will set offset value to -1, so code that use reloptions + * can deal this case. For better readability it was defined as a constant. + */ +#define OPTION_STRING_VALUE_NOT_SET_OFFSET -1 + +typedef struct option_spec_string +{ + option_spec_basic base; + validate_string_option validate_cb; + fill_string_option fill_cb; + char *default_val; +} option_spec_string; + +typedef void (*postprocess_bytea_options_function) (void *data, bool validate); + +typedef struct options_spec_set +{ + option_spec_basic **definitions; + int num; /* Number of spec_set items in use */ + int num_allocated; /* Number of spec_set items allocated */ + bool assert_on_realloc; /* If number of items of the spec_set were + * strictly set to certain value assert on + * adding more items */ + bool is_local; /* If true specset is in local memory context */ + Size struct_size; /* Size of a structure for options in binary + * representation */ + postprocess_bytea_options_function postprocess_fun; /* This function is + * called after options + * were converted in + * Bytea representation. + * Can be used for extra + * validation etc. */ + char *namspace; /* Spec Set is used for options from this + * namespase */ +} options_spec_set; + + +/* holds an option value parsed or unparsed */ +typedef struct option_value +{ + option_spec_basic *gen; + char *namspace; + option_value_status status; + char *raw_value; /* allocated separately */ + char *raw_name; + union + { + bool bool_val; + int int_val; + double real_val; + int enum_val; + char *string_val; /* allocated separately */ + } values; +} option_value; + + +/* + * Options Spec Set related functions + */ +extern options_spec_set *allocateOptionsSpecSet(const char *namspace, + int bytea_size, bool is_local, int num_items_expected); + +extern void optionsSpecSetAddBool(options_spec_set *spec_set, const char *name, + const char *desc, LOCKMODE lockmode, + int struct_offset, + option_value_postvalidate postvalidate_fn, + bool default_val); + +extern void optionsSpecSetAddInt(options_spec_set *spec_set, const char *name, + const char *desc, LOCKMODE lockmode, + int struct_offset, + option_value_postvalidate postvalidate_fn, + int default_val, int min_val, int max_val); + +extern void optionsSpecSetAddReal(options_spec_set *spec_set, const char *name, + const char *desc, LOCKMODE lockmode, + int struct_offset, + option_value_postvalidate postvalidate_fn, + double default_val, double min_val, double max_val); + +extern void optionsSpecSetAddEnum(options_spec_set *spec_set, const char *name, + const char *desc, LOCKMODE lockmode, + int struct_offset, + option_value_postvalidate postvalidate_fn, + opt_enum_elt_def *members, int default_val, + const char *detailmsg); + + +extern void optionsSpecSetAddString(options_spec_set *spec_set, + const char *name, const char *desc, + LOCKMODE lockmode, int struct_offset, + option_value_postvalidate postvalidate_fn, + const char *default_val, + validate_string_option validator, + fill_string_option filler); + + +/* + * This macro allows to get string option value from bytea representation. + * "optstruct" - is a structure that is stored in bytea options representation + * "member" - member of this structure that has string option value + * (actually string values are stored in bytea after the structure, and + * and "member" will contain an offset to this value. This macro do all + * the math + */ +#define GET_STRING_OPTION(optstruct, member) \ + ((optstruct)->member == OPTION_STRING_VALUE_NOT_SET_OFFSET ? NULL : \ + (char *)(optstruct) + (optstruct)->member) + +/* + * Functions related to option conversion, parsing, manipulation + * and validation + */ +extern void optionsDefListValdateNamespaces(List *defList, + const char * const* allowed_namspaces); +extern List *optionsDefListFilterNamespaces(List *defList, + const char *namspace); +extern List *optionsTextArrayToDefList(Datum options); +extern Datum optionsDefListToTextArray(List *defList); + +/* + * Meta functions that uses functions above to get options for relations, + * tablespaces, views and so on + */ + +extern bytea *optionsTextArrayToBytea(options_spec_set *spec_set, Datum data, + bool validate); +extern Datum optionsUpdateTexArrayWithDefList(options_spec_set *spec_set, + Datum oldOptions, List *defList, bool do_reset); +extern Datum optionDefListToTextArray(options_spec_set *spec_set, + List *defList); + +/* Internal functions */ + +extern List *optionsTextArrayToRawValues(Datum array_datum); +extern List *optionsParseRawValues(List *raw_values, + options_spec_set *spec_set, bool validate); +extern bytea *optionsValuesToBytea(List *options, options_spec_set *spec_set); + + +#endif /* OPTIONS_H */ diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 43445cdcc6c..b42bb614e03 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -1,14 +1,9 @@ /*------------------------------------------------------------------------- * * reloptions.h - * Core support for relation and tablespace options (pg_class.reloptions + * Support for relation view and tablespace options (pg_class.reloptions * and pg_tablespace.spcoptions) * - * Note: the functions dealing with text-array reloptions values declare - * them as Datum, not ArrayType *, to avoid needing to include array.h - * into a lot of low-level code. - * - * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -25,175 +20,36 @@ #include "nodes/pg_list.h" #include "storage/lock.h" -/* types supported by reloptions */ -typedef enum relopt_type -{ - RELOPT_TYPE_BOOL, - RELOPT_TYPE_INT, - RELOPT_TYPE_REAL, - RELOPT_TYPE_ENUM, - RELOPT_TYPE_STRING, -} relopt_type; - -/* kinds supported by reloptions */ -typedef enum relopt_kind -{ - RELOPT_KIND_LOCAL = 0, - RELOPT_KIND_HEAP = (1 << 0), - RELOPT_KIND_TOAST = (1 << 1), - RELOPT_KIND_BTREE = (1 << 2), - RELOPT_KIND_HASH = (1 << 3), - RELOPT_KIND_GIN = (1 << 4), - RELOPT_KIND_GIST = (1 << 5), - RELOPT_KIND_ATTRIBUTE = (1 << 6), - RELOPT_KIND_TABLESPACE = (1 << 7), - RELOPT_KIND_SPGIST = (1 << 8), - RELOPT_KIND_VIEW = (1 << 9), - RELOPT_KIND_BRIN = (1 << 10), - RELOPT_KIND_PARTITIONED = (1 << 11), - /* if you add a new kind, make sure you update "last_default" too */ - RELOPT_KIND_LAST_DEFAULT = RELOPT_KIND_PARTITIONED, - /* some compilers treat enums as signed ints, so we can't use 1 << 31 */ - RELOPT_KIND_MAX = (1 << 30) -} relopt_kind; - /* reloption namespaces allowed for heaps -- currently only TOAST */ #define HEAP_RELOPT_NAMESPACES { "toast", NULL } -/* generic struct to hold shared data */ -typedef struct relopt_gen -{ - const char *name; /* must be first (used as list termination - * marker) */ - const char *desc; - bits32 kinds; - LOCKMODE lockmode; - int namelen; - relopt_type type; -} relopt_gen; - -/* holds a parsed value */ -typedef struct relopt_value -{ - relopt_gen *gen; - bool isset; - union - { - bool bool_val; - int int_val; - double real_val; - int enum_val; - char *string_val; /* allocated separately */ - } values; -} relopt_value; - -/* reloptions records for specific variable types */ -typedef struct relopt_bool -{ - relopt_gen gen; - bool default_val; -} relopt_bool; - -typedef struct relopt_int -{ - relopt_gen gen; - int default_val; - int min; - int max; -} relopt_int; - -typedef struct relopt_real -{ - relopt_gen gen; - double default_val; - double min; - double max; -} relopt_real; - /* - * relopt_enum_elt_def -- One member of the array of acceptable values - * of an enum reloption. + * backward compatibility aliases so local reloption code of custom validator + * can work. */ -typedef struct relopt_enum_elt_def -{ - const char *string_val; - int symbol_val; -} relopt_enum_elt_def; +typedef option_value relopt_value; +typedef fill_string_option fill_string_relopt; +typedef validate_string_option validate_string_relopt; +#define GET_STRING_RELOPTION(optstruct, member) \ + GET_STRING_OPTION(optstruct, member) -typedef struct relopt_enum -{ - relopt_gen gen; - relopt_enum_elt_def *members; - int default_val; - const char *detailmsg; - /* null-terminated array of members */ -} relopt_enum; -/* validation routines for strings */ -typedef void (*validate_string_relopt) (const char *value); -typedef Size (*fill_string_relopt) (const char *value, void *ptr); +/* + * relopts_validator functions is left for backward compatibility for using + * with local reloptions. Should not be used elsewhere + */ /* validation routine for the whole option set */ -typedef void (*relopts_validator) (void *parsed_options, relopt_value *vals, int nvals); - -typedef struct relopt_string -{ - relopt_gen gen; - int default_len; - bool default_isnull; - validate_string_relopt validate_cb; - fill_string_relopt fill_cb; - char *default_val; -} relopt_string; - -/* This is the table datatype for build_reloptions() */ -typedef struct -{ - const char *optname; /* option's name */ - relopt_type opttype; /* option's datatype */ - int offset; /* offset of field in result struct */ -} relopt_parse_elt; - -/* Local reloption definition */ -typedef struct local_relopt -{ - relopt_gen *option; /* option definition */ - int offset; /* offset of parsed value in bytea structure */ -} local_relopt; +typedef void (*relopts_validator) (void *parsed_options, relopt_value *vals, + int nvals); /* Structure to hold local reloption data for build_local_reloptions() */ typedef struct local_relopts { - List *options; /* list of local_relopt definitions */ List *validators; /* list of relopts_validator callbacks */ - Size relopt_struct_size; /* size of parsed bytea structure */ + options_spec_set *spec_set; /* Spec Set to store options info */ } local_relopts; -/* - * Utility macro to get a value for a string reloption once the options - * are parsed. This gets a pointer to the string value itself. "optstruct" - * is the StdRdOptions struct or equivalent, "member" is the struct member - * corresponding to the string option. - */ -#define GET_STRING_RELOPTION(optstruct, member) \ - ((optstruct)->member == 0 ? NULL : \ - (char *)(optstruct) + (optstruct)->member) - -extern relopt_kind add_reloption_kind(void); -extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, - bool default_val, LOCKMODE lockmode); -extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, - int default_val, int min_val, int max_val, - LOCKMODE lockmode); -extern void add_real_reloption(bits32 kinds, const char *name, const char *desc, - double default_val, double min_val, double max_val, - LOCKMODE lockmode); -extern void add_enum_reloption(bits32 kinds, const char *name, const char *desc, - relopt_enum_elt_def *members, int default_val, - const char *detailmsg, LOCKMODE lockmode); -extern void add_string_reloption(bits32 kinds, const char *name, const char *desc, - const char *default_val, validate_string_relopt validator, - LOCKMODE lockmode); extern void init_local_reloptions(local_relopts *relopts, Size relopt_struct_size); extern void register_reloptions_validator(local_relopts *relopts, @@ -210,7 +66,7 @@ extern void add_local_real_reloption(local_relopts *relopts, const char *name, int offset); extern void add_local_enum_reloption(local_relopts *relopts, const char *name, const char *desc, - relopt_enum_elt_def *members, + opt_enum_elt_def *members, int default_val, const char *detailmsg, int offset); extern void add_local_string_reloption(local_relopts *relopts, const char *name, @@ -219,29 +75,17 @@ extern void add_local_string_reloption(local_relopts *relopts, const char *name, validate_string_relopt validator, fill_string_relopt filler, int offset); -extern Datum transformRelOptions(Datum oldOptions, List *defList, - const char *namspace, const char *const validnsps[], - bool acceptOidsOff, bool isReset); -extern List *untransformRelOptions(Datum options); extern bytea *extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, - amoptions_function amoptions); -extern void *build_reloptions(Datum reloptions, bool validate, - relopt_kind kind, - Size relopt_struct_size, - const relopt_parse_elt *relopt_elems, - int num_relopt_elems); + amreloptspecset_function amoptions_def_set); extern void *build_local_reloptions(local_relopts *relopts, Datum options, bool validate); -extern bytea *default_reloptions(Datum reloptions, bool validate, - relopt_kind kind); -extern bytea *heap_reloptions(char relkind, Datum reloptions, bool validate); -extern bytea *view_reloptions(Datum reloptions, bool validate); -extern bytea *partitioned_table_reloptions(Datum reloptions, bool validate); -extern bytea *index_reloptions(amoptions_function amoptions, Datum reloptions, - bool validate); -extern bytea *attribute_reloptions(Datum reloptions, bool validate); -extern bytea *tablespace_reloptions(Datum reloptions, bool validate); -extern LOCKMODE AlterTableGetRelOptionsLockLevel(List *defList); +options_spec_set *get_heap_relopt_spec_set(void); +options_spec_set *get_toast_relopt_spec_set(void); +options_spec_set *get_partitioned_relopt_spec_set(void); +options_spec_set *get_view_relopt_spec_set(void); +options_spec_set *get_attribute_options_spec_set(void); +options_spec_set *get_tablespace_options_spec_set(void); +extern LOCKMODE AlterTableGetRelOptionsLockLevel(Relation rel, List *defList); #endif /* RELOPTIONS_H */ diff --git a/src/include/access/spgist.h b/src/include/access/spgist.h index cbe9b347d8f..c92c8698b98 100644 --- a/src/include/access/spgist.h +++ b/src/include/access/spgist.h @@ -189,9 +189,6 @@ typedef struct spgLeafConsistentOut } spgLeafConsistentOut; -/* spgutils.c */ -extern bytea *spgoptions(Datum reloptions, bool validate); - /* spginsert.c */ extern IndexBuildResult *spgbuild(Relation heap, Relation index, struct IndexInfo *indexInfo); diff --git a/src/include/access/spgist_private.h b/src/include/access/spgist_private.h index cb43a278f46..9f78a726646 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -530,6 +530,7 @@ extern OffsetNumber SpGistPageAddNewItem(SpGistState *state, Page page, extern bool spgproperty(Oid index_oid, int attno, IndexAMProperty prop, const char *propname, bool *res, bool *isnull); +extern options_spec_set *spggetreloptspecset(void); /* spgdoinsert.c */ extern void spgUpdateNodeLink(SpGistInnerTuple tup, int nodeN, diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index 6832470d387..03a656cb4a8 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -36,7 +36,7 @@ extern Oid AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode); extern void AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode, struct AlterTableUtilityContext *context); -extern LOCKMODE AlterTableGetLockLevel(List *cmds); +extern LOCKMODE AlterTableGetLockLevel(Oid relid, List *cmds); extern void ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lockmode); diff --git a/src/test/modules/dummy_index_am/dummy_index_am.c b/src/test/modules/dummy_index_am/dummy_index_am.c index 7586f8ae5e1..97467b73c92 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,18 +14,14 @@ #include "postgres.h" #include "access/amapi.h" -#include "access/reloptions.h" +#include "access/options.h" #include "catalog/index.h" #include "commands/vacuum.h" #include "nodes/pathnodes.h" PG_MODULE_MAGIC; -/* parse table for fillRelOptions */ -static relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -static relopt_kind di_relopt_kind; +void _PG_init(void); typedef enum DummyAmEnum { @@ -45,7 +41,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -static relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -59,77 +55,82 @@ PG_FUNCTION_INFO_V1(dihandler); * Validation function for string relation options. */ static void -validate_string_option(const char *value) +divalidate_string_option(const char *value) { ereport(NOTICE, (errmsg("new option value for string parameter %s", value ? value : "NULL"))); } -/* - * This function creates a full set of relation option types, - * with various patterns. - */ -static void -create_reloptions_table(void) +static options_spec_set *di_relopt_specset = NULL; +options_spec_set *digetreloptspecset(void); + +options_spec_set * +digetreloptspecset(void) { - di_relopt_kind = add_reloption_kind(); - - add_int_reloption(di_relopt_kind, "option_int", - "Integer option for dummy_index_am", - 10, -10, 100, AccessExclusiveLock); - di_relopt_tab[0].optname = "option_int"; - di_relopt_tab[0].opttype = RELOPT_TYPE_INT; - di_relopt_tab[0].offset = offsetof(DummyIndexOptions, option_int); - - add_real_reloption(di_relopt_kind, "option_real", - "Real option for dummy_index_am", - 3.1415, -10, 100, AccessExclusiveLock); - di_relopt_tab[1].optname = "option_real"; - di_relopt_tab[1].opttype = RELOPT_TYPE_REAL; - di_relopt_tab[1].offset = offsetof(DummyIndexOptions, option_real); - - add_bool_reloption(di_relopt_kind, "option_bool", - "Boolean option for dummy_index_am", - true, AccessExclusiveLock); - di_relopt_tab[2].optname = "option_bool"; - di_relopt_tab[2].opttype = RELOPT_TYPE_BOOL; - di_relopt_tab[2].offset = offsetof(DummyIndexOptions, option_bool); - - add_enum_reloption(di_relopt_kind, "option_enum", - "Enum option for dummy_index_am", - dummyAmEnumValues, - DUMMY_AM_ENUM_ONE, - "Valid values are \"one\" and \"two\".", - AccessExclusiveLock); - di_relopt_tab[3].optname = "option_enum"; - di_relopt_tab[3].opttype = RELOPT_TYPE_ENUM; - di_relopt_tab[3].offset = offsetof(DummyIndexOptions, option_enum); - - add_string_reloption(di_relopt_kind, "option_string_val", - "String option for dummy_index_am with non-NULL default", - "DefaultValue", &validate_string_option, - AccessExclusiveLock); - di_relopt_tab[4].optname = "option_string_val"; - di_relopt_tab[4].opttype = RELOPT_TYPE_STRING; - di_relopt_tab[4].offset = offsetof(DummyIndexOptions, - option_string_val_offset); + if (di_relopt_specset) + return di_relopt_specset; + + di_relopt_specset = allocateOptionsSpecSet(NULL, + sizeof(DummyIndexOptions), false, 6); + + optionsSpecSetAddInt( + di_relopt_specset, "option_int", + "Integer option for dummy_index_am", + AccessExclusiveLock, + offsetof(DummyIndexOptions, option_int), NULL, + 10, -10, 100 + ); + + + optionsSpecSetAddReal( + di_relopt_specset, "option_real", + "Real option for dummy_index_am", + AccessExclusiveLock, + offsetof(DummyIndexOptions, option_real), NULL, + 3.1415, -10, 100 + ); + + optionsSpecSetAddBool( + di_relopt_specset, "option_bool", + "Boolean option for dummy_index_am", + AccessExclusiveLock, + offsetof(DummyIndexOptions, option_bool), NULL, true + ); + + optionsSpecSetAddEnum(di_relopt_specset, "option_enum", + "Enum option for dummy_index_am", + AccessExclusiveLock, + offsetof(DummyIndexOptions, option_enum), NULL, + dummyAmEnumValues, + DUMMY_AM_ENUM_ONE, + "Valid values are \"one\" and \"two\"." + ); + + optionsSpecSetAddString(di_relopt_specset, "option_string_val", + "String option for dummy_index_am with non-NULL default", + AccessExclusiveLock, + offsetof(DummyIndexOptions, option_string_val_offset), NULL, + "DefaultValue", &divalidate_string_option, NULL + ); /* * String option for dummy_index_am with NULL default, and without * description. */ - add_string_reloption(di_relopt_kind, "option_string_null", - NULL, /* description */ - NULL, &validate_string_option, - AccessExclusiveLock); - di_relopt_tab[5].optname = "option_string_null"; - di_relopt_tab[5].opttype = RELOPT_TYPE_STRING; - di_relopt_tab[5].offset = offsetof(DummyIndexOptions, - option_string_null_offset); + + optionsSpecSetAddString(di_relopt_specset, "option_string_null", + NULL, /* description */ + AccessExclusiveLock, + offsetof(DummyIndexOptions, option_string_null_offset), NULL, + NULL, &divalidate_string_option, NULL + ); + + return di_relopt_specset; } + /* * Build a new index. */ @@ -214,19 +215,6 @@ dicostestimate(PlannerInfo *root, IndexPath *path, double loop_count, *indexPages = 1; } -/* - * Parse relation options for index AM, returning a DummyIndexOptions - * structure filled with option values. - */ -static bytea * -dioptions(Datum reloptions, bool validate) -{ - return (bytea *) build_reloptions(reloptions, validate, - di_relopt_kind, - sizeof(DummyIndexOptions), - di_relopt_tab, lengthof(di_relopt_tab)); -} - /* * Validator for index AM. */ @@ -307,7 +295,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; amroutine->amgettreeheight = NULL; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -321,12 +308,7 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = digetreloptspecset; PG_RETURN_POINTER(amroutine); } - -void -_PG_init(void) -{ - create_reloptions_table(); -} diff --git a/src/test/modules/test_oat_hooks/expected/alter_table.out b/src/test/modules/test_oat_hooks/expected/alter_table.out index 8cbacca2c9b..8319cd06941 100644 --- a/src/test/modules/test_oat_hooks/expected/alter_table.out +++ b/src/test/modules/test_oat_hooks/expected/alter_table.out @@ -82,6 +82,8 @@ ALTER TABLE test_oat_schema.test_oat_tab ENABLE ROW LEVEL SECURITY; NOTICE: in process utility: superuser attempting ALTER TABLE NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser finished alter (subId=0x0) [explicit without auxiliary object] NOTICE: in process utility: superuser finished ALTER TABLE @@ -89,6 +91,8 @@ ALTER TABLE test_oat_schema.test_oat_tab DISABLE ROW LEVEL SECURITY; NOTICE: in process utility: superuser attempting ALTER TABLE NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser finished alter (subId=0x0) [explicit without auxiliary object] NOTICE: in process utility: superuser finished ALTER TABLE @@ -96,6 +100,8 @@ ALTER TABLE test_oat_schema.test_oat_tab FORCE ROW LEVEL SECURITY; NOTICE: in process utility: superuser attempting ALTER TABLE NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser finished alter (subId=0x0) [explicit without auxiliary object] NOTICE: in process utility: superuser finished ALTER TABLE @@ -103,6 +109,8 @@ ALTER TABLE test_oat_schema.test_oat_tab NO FORCE ROW LEVEL SECURITY; NOTICE: in process utility: superuser attempting ALTER TABLE NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser finished alter (subId=0x0) [explicit without auxiliary object] NOTICE: in process utility: superuser finished ALTER TABLE @@ -111,6 +119,8 @@ ALTER TABLE test_oat_schema.test_oat_tab DISABLE RULE test_oat_notify; NOTICE: in process utility: superuser attempting ALTER TABLE NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser finished alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] @@ -120,6 +130,8 @@ ALTER TABLE test_oat_schema.test_oat_tab ENABLE RULE test_oat_notify; NOTICE: in process utility: superuser attempting ALTER TABLE NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser finished alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] @@ -130,6 +142,8 @@ ALTER TABLE test_oat_schema.test_oat_tab DISABLE TRIGGER test_oat_trigger; NOTICE: in process utility: superuser attempting ALTER TABLE NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser finished alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] @@ -139,6 +153,8 @@ ALTER TABLE test_oat_schema.test_oat_tab ENABLE TRIGGER test_oat_trigger; NOTICE: in process utility: superuser attempting ALTER TABLE NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser attempting namespace search (subId=0x0) [report on violation, allowed] +NOTICE: in object access: superuser finished namespace search (subId=0x0) [report on violation, allowed] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser finished alter (subId=0x0) [explicit without auxiliary object] NOTICE: in object access: superuser attempting alter (subId=0x0) [explicit without auxiliary object] diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..67f0a828f9f 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -175,7 +175,7 @@ SELECT reloptions FROM pg_class WHERE oid = :toast_oid; -- Fail on non-existent options in toast namespace CREATE TABLE reloptions_test2 (i int) WITH (toast.not_existing_option = 42); -ERROR: unrecognized parameter "not_existing_option" +ERROR: unrecognized parameter "toast.not_existing_option" -- Mix TOAST & heap DROP TABLE reloptions_test; CREATE TABLE reloptions_test (s VARCHAR) WITH @@ -194,6 +194,17 @@ SELECT reloptions FROM pg_class WHERE oid = ( {autovacuum_vacuum_cost_delay=23} (1 row) +-- Can reset option that is not allowed, but for some reason is already set +UPDATE pg_class + SET reloptions = '{fillfactor=13,autovacuum_enabled=false,illegal_option=4}' + WHERE oid = 'reloptions_test'::regclass; +ALTER TABLE reloptions_test RESET (illegal_option); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------------------------ + {fillfactor=13,autovacuum_enabled=false} +(1 row) + -- -- CREATE INDEX, ALTER INDEX for btrees -- diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..9b404295c05 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -112,6 +112,13 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; SELECT reloptions FROM pg_class WHERE oid = ( SELECT reltoastrelid FROM pg_class WHERE oid = 'reloptions_test'::regclass); +-- Can reset option that is not allowed, but for some reason is already set +UPDATE pg_class + SET reloptions = '{fillfactor=13,autovacuum_enabled=false,illegal_option=4}' + WHERE oid = 'reloptions_test'::regclass; +ALTER TABLE reloptions_test RESET (illegal_option); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- -- CREATE INDEX, ALTER INDEX for btrees -- -- 2.39.2 [application/pgp-signature] signature.asc (488B, ../../39561561.10thIPus4b@thinkpad-pgpro/3-signature.asc) download ^ permalink raw reply [nested|flat] 3+ messages in thread
end of thread, other threads:[~2025-02-26 19:33 UTC | newest] Thread overview: 3+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2020-11-11 12:51 [PATCH v14 1/2] In-place table persistence change Kyotaro Horiguchi <[email protected]> 2025-02-10 18:35 Re: [PATCH] New [relation] option engine Nikolay Shaplov <[email protected]> 2025-02-26 19:33 ` Re: [PATCH] New [relation] option engine Nikolay Shaplov <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox