agora inbox for pgsql-hackers@postgresql.orghelp / color / mirror / Atom feed
[PATCH] New options engine 22+ messages / 2 participants [nested] [flat]
* [PATCH] New options engine @ 2022-05-16 14:13 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2022-05-16 14:13 UTC (permalink / raw) 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 | 111 +- contrib/bloom/expected/bloom.out | 3 + contrib/bloom/sql/bloom.sql | 3 + 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 +- src/backend/access/brin/brin.c | 47 +- src/backend/access/common/Makefile | 1 + src/backend/access/common/options.c | 1364 ++++++++++ src/backend/access/common/reloptions.c | 2317 ++++------------- 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 | 19 +- src/backend/access/spgist/spgutils.c | 41 +- src/backend/access/transam/xact.c | 2 +- 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/bin/pg_basebackup/streamutil.c | 2 +- src/include/access/amapi.h | 9 +- src/include/access/brin.h | 2 + src/include/access/brin_internal.h | 2 + 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 | 262 ++ src/include/access/reloptions.h | 205 +- 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 +- src/test/regress/expected/reloptions.out | 13 +- src/test/regress/sql/reloptions.sql | 7 + src/tools/pgindent/typedefs.list | 28 +- 49 files changed, 2636 insertions(+), 2379 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 9966f3f46e..9a009bdbb3 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" @@ -207,7 +208,8 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *bloptions(Datum reloptions, bool validate); +extern void *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 a434cf93ef..6587a721c4 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 "catalog/index.h" #include "commands/vacuum.h" @@ -34,52 +34,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->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; - amroutine->amoptions = bloptions; + amroutine->amreloptspecset = blrelopt_specset; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = blvalidate; @@ -154,6 +114,24 @@ blhandler(PG_FUNCTION_ARGS) PG_RETURN_POINTER(amroutine); } +void +blReloptionPostprocess(void *data, bool validate) +{ + BloomOptions *opts = (BloomOptions *) data; + int i; + + if (validate) + for (i = 0; i < INDEX_MAX_KEYS; i++) + if (opts->bitSize[i] >= opts->bloomLength) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("col%i should not be grater than length", i))); + + /* Convert signature length from # of bits to # to words, rounding up */ + opts->bloomLength = (opts->bloomLength + SIGNWORDBITS - 1) / SIGNWORDBITS; +} + + /* * Fill BloomState structure for particular index. */ @@ -474,24 +452,37 @@ BloomInitMetapage(Relation index) UnlockReleaseBuffer(metaBuffer); } -/* - * Parse reloptions for bloom index, producing a BloomOptions struct. - */ -bytea * -bloptions(Datum reloptions, bool validate) +void * +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/bloom/expected/bloom.out b/contrib/bloom/expected/bloom.out index dae12a7d3e..e79456dbce 100644 --- a/contrib/bloom/expected/bloom.out +++ b/contrib/bloom/expected/bloom.out @@ -228,3 +228,6 @@ CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=0); ERROR: value 0 out of bounds for option "length" CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (col1=0); ERROR: value 0 out of bounds for option "col1" +-- check post_validate for colN<lengh +CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=10,col1=11); +ERROR: col0 should not be grater than length diff --git a/contrib/bloom/sql/bloom.sql b/contrib/bloom/sql/bloom.sql index 4733e1e705..0bfc767faf 100644 --- a/contrib/bloom/sql/bloom.sql +++ b/contrib/bloom/sql/bloom.sql @@ -93,3 +93,6 @@ SELECT reloptions FROM pg_class WHERE oid = 'bloomidx'::regclass; \set VERBOSITY terse CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=0); CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (col1=0); + +-- check post_validate for colN<lengh +CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=10,col1=11); diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c index a561d1d652..2aafe2df83 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -1977,7 +1977,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 4773cadec0..1de15ce09b 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -195,7 +195,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 572591a558..c56457140f 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -73,7 +73,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 e89dc74a5e..af46f0a3ee 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/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 52f171772d..a1eb51bcd6 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" @@ -40,7 +39,6 @@ #include "utils/memutils.h" #include "utils/rel.h" - /* * We use a BrinBuildState during initial construction of a BRIN index. * The running state is kept in a BrinMemTuple. @@ -120,7 +118,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -135,6 +132,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -963,23 +961,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. @@ -1785,3 +1766,29 @@ check_null_keys(BrinValues *bval, ScanKey *nullkeys, int nnullkeys) return true; } + +static options_spec_set *brin_relopt_specset = NULL; + +void * +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 b9aff0ccfd..78c9c5a8d2 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/options.c b/src/backend/access/common/options.c new file mode 100644 index 0000000000..499e98c274 --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1364 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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 therm "specification" instead of "definition" because therm + * "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 Vale 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 vales. 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 + offest. + */ + +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 Option 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. + * + * size_of_bytea - 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 *namespace, 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 (namespace) + { + spec_set->namespace = palloc(strlen(namespace) + 1); + strcpy(spec_set->namespace, namespace); + } + else + spec_set->namespace = NULL; + if (num_items_expected > 0) + { + spec_set->num_allocated = num_items_expected; + spec_set->forbid_realloc = true; + spec_set->definitions = palloc( + spec_set->num_allocated * sizeof(option_spec_basic *)); + } + else + { + spec_set->num_allocated = 0; + spec_set->forbid_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", 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 objec 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->forbid_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->namespace = palloc(strlen(def->defnamespace) + 1); + strcpy(option_dst->namespace, def->defnamespace); + } + else + option_dst->namespace = 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(array, TEXTOID, -1, false, 'i', + &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 i; + 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 (i = 0; i < text_len; i = i + pg_mblen(text_str)) + { + if (text_str[i] == '=') + { + name_len = i; + 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->namespace = 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_namespaces array. Items without namespace are also accepted + * + * Used while validation of syntax parser output. Error is thrown if unproper + * namespace is found. + */ +void +optionsDefListValdateNamespaces(List *defList, char **allowed_namespaces) +{ + 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_namespaces[i]) + { + if (strcmp(def->defnamespace, + allowed_namespaces[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 "namespace" namespace. If "namespace" + * is NULL, only namespaseless options are returned + */ +List * +optionsDefListFilterNamespaces(List *defList, const char *namespace) +{ + ListCell *cell; + List *result = NIL; + + foreach(cell, defList) + { + DefElem *def = (DefElem *) lfirst(cell); + + if ((!namespace && !def->defnamespace) || + (namespace && def->defnamespace && + strcmp(namespace, 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(array, TEXTOID, -1, false, 'i', + &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->namespace && (!spec_set->namespace || + strcmp(spec_set->namespace, option->namespace) != 0)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter namespace \"%s\"", + option->namespace))); + + 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->namespace) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter \"%s.%s\"", + option->namespace, 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", 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", + 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 = optionsValuesToBytea(new_values, spec_set); + + spec_set->postprocess_fun(data, true); + 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 24211807ca..b01462d0cd 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-2022, 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" @@ -36,6 +33,7 @@ #include "utils/guc.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "storage/bufmgr.h" /* * Contents of pg_class.reloptions @@ -93,389 +91,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_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}, @@ -489,17 +106,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}, @@ -507,225 +115,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_for_toast); +void oid_postvalidate(option_value *value); /* * init_local_reloptions @@ -735,9 +127,8 @@ add_reloption(relopt_gen *newoption) void init_local_reloptions(local_relopts *opts, Size relopt_struct_size) { - opts->options = NIL; opts->validators = NIL; - opts->relopt_struct_size = relopt_struct_size; + opts->spec_set = allocateOptionsSpecSet(NULL, relopt_struct_size, true, 0); } /* @@ -751,112 +142,6 @@ register_reloptions_validator(local_relopts *opts, relopts_validator validator) opts->validators = lappend(opts->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 @@ -867,504 +152,71 @@ 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); -} - -/* - * add_local_int_reloption - * Add a new local integer reloption - * - * 'offset' is offset of int-typed field. - */ -void -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); -} - -/* - * add_local_real_reloption - * Add a new local float reloption - * - * '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; + optionsSpecSetAddBool(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val); } - -/* - * 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; - } - - return newoption; -} - -/* - * add_string_reloption - * Add a new string 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. - */ -void -add_string_reloption(bits32 kinds, const char *name, const char *desc, - const char *default_val, validate_string_relopt validator, - LOCKMODE lockmode) -{ - relopt_string *newoption = init_string_reloption(kinds, name, desc, - default_val, - validator, NULL, - lockmode); - - add_reloption((relopt_gen *) newoption); -} - -/* - * add_local_string_reloption - * Add a new local string reloption - * - * 'offset' is offset of int-typed field that will store offset of string value - * in the resulting bytea structure. - */ -void -add_local_string_reloption(local_relopts *relopts, const char *name, - const char *desc, const char *default_val, - 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, - char *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(array, TEXTOID, -1, false, TYPALIGN_INT, - &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(). + * add_local_int_reloption + * Add a new local integer reloption + * + * 'offset' is offset of int-typed field. */ -List * -untransformRelOptions(Datum options) +void +add_local_int_reloption(local_relopts *relopts, const char *name, + const char *desc, int default_val, int min_val, + int max_val, int offset) { - 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); + optionsSpecSetAddInt(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); +} - deconstruct_array(array, TEXTOID, -1, false, TYPALIGN_INT, - &optiondatums, NULL, &noptions); +/* + * add_local_real_reloption + * Add a new local float reloption + * + * '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) +{ + optionsSpecSetAddReal(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); - 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)); - } +/* + * 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, opt_enum_elt_def *members, + int default_val, const char *detailmsg, int offset) +{ + optionsSpecSetAddEnum(relopts->spec_set, name, desc, NoLock, offset, NULL, + members, default_val, detailmsg); +} - return result; +/* + * add_local_string_reloption + * Add a new local string reloption + * + * 'offset' is offset of int-typed field that will store offset of string value + * in the resulting bytea structure. + */ +void +add_local_string_reloption(local_relopts *relopts, const char *name, + const char *desc, const char *default_val, + validate_string_relopt validator, + fill_string_relopt filler, int offset) +{ + optionsSpecSetAddString(relopts->spec_set, name, desc, NoLock, offset, + NULL, default_val, validator, filler); } /* @@ -1381,12 +233,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, @@ -1400,545 +253,308 @@ 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(array, TEXTOID, -1, false, TYPALIGN_INT, - &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. - * - * options is a reloption text array as constructed by transformRelOptions. - * kind specifies the family of options to be processed. - * - * 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. - * - * 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). - * - * 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. + * 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 relopt_value * -parseRelOptions(Datum options, bool validate, relopt_kind kind, - int *numrelopts) +static void +toast_options_postprocess(void *data, bool validate) { - 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++; + StdRdOptions *toast_options = (StdRdOptions *) data; - 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++; - } - } - } - - /* Done if no options */ - if (PointerIsValid(DatumGetPointer(options))) - parseRelOptionsInternal(options, validate, reloptions, numoptions); - - *numrelopts = numoptions; - return reloptions; + toast_options->fillfactor = 100; + toast_options->autovacuum.analyze_threshold = -1; + toast_options->autovacuum.analyze_scale_factor = -1; } -/* 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; - - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); - - values[i].gen = opt->option; - values[i].isset = false; - - i++; - } - - if (options != (Datum) 0) - parseRelOptionsInternal(options, validate, values, nopts); - - return values; -} /* - * Subroutine for parseRelOptions, to parse and validate a single option's - * value + * 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. + * + * Autovacuum related parameters can be set at ShareUpdateExclusiveLock + * since they are only used by the AV procs and don't change anything + * currently executing. + * + * Fillfactor can be set because it applies only to subsequent changes made to + * data blocks, as documented in heapio.c + * + * 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. + * + * 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. */ -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; +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) + stdrd_relopt_spec_set->postprocess_fun = toast_options_postprocess; + + 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); + + 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); } - if (parsed) - option->isset = true; - if (!nofree) - pfree(value); -} - -/* - * 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). - */ -static void * -allocateReloptStruct(Size base, relopt_value *options, int numoptions) -{ - Size size = base; - int i; - - for (i = 0; i < numoptions; i++) - { - relopt_value *optval = &options[i]; - - if (optval->gen->type == RELOPT_TYPE_STRING) - { - relopt_string *optstr = (relopt_string *) optval->gen; + 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\".")); - if (optstr->fill_cb) - { - const char *val = optval->isset ? optval->values.string_val : - optstr->default_isnull ? NULL : optstr->default_val; + 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); - size += optstr->fill_cb(val, NULL); - } - else - size += GET_STRING_RELOPTION_LEN(*optval) + 1; - } - } + 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 palloc0(size); + return stdrd_relopt_spec_set; } -/* - * 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) -{ - int i; - int offset = basesize; - for (i = 0; i < numoptions; i++) - { - int j; - bool found = false; +static options_spec_set *heap_relopt_spec_set = NULL; - 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); +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; } +static options_spec_set *toast_relopt_spec_set = NULL; -/* - * Option parser for anything that uses StdRdOptions. - */ -bytea * -default_reloptions(Datum reloptions, bool validate, relopt_kind kind) +options_spec_set * +get_toast_relopt_spec_set(void) { - 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_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 (toast_relopt_spec_set) + return toast_relopt_spec_set; + toast_relopt_spec_set = get_stdrd_relopt_spec_set(false); + return toast_relopt_spec_set; } -/* - * 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); +static options_spec_set *partitioned_relopt_spec_set = NULL; - /* if none set, we're done */ - if (numoptions == 0) - { - Assert(options == NULL); - return 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); - - pfree(options); +options_spec_set * +get_partitioned_relopt_spec_set(void) +{ + if (partitioned_relopt_spec_set) + return partitioned_relopt_spec_set; + partitioned_relopt_spec_set = allocateOptionsSpecSet( + NULL, sizeof(StdRdOptions), false, 0); + /* No options for now, so spec set is empty */ - return rdopts; + return partitioned_relopt_spec_set; } /* @@ -1949,158 +565,167 @@ 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; - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); + 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 + */ - elems[i].optname = opt->option->name; - elems[i].opttype = opt->option->type; - elems[i].offset = opt->offset; + if (validate && relopts->validators) + { + int val_count = 0; + int i; + option_value *val_array; - i++; - } + foreach(lc, values) + val_count++; + val_array = palloc(sizeof(option_value) * val_count); - vals = parseLocalRelOptions(relopts, options, validate); - opts = allocateReloptStruct(relopts->relopt_struct_size, vals, noptions); - fillRelOptions(opts, relopts->relopt_struct_size, vals, noptions, validate, - elems, noptions); + i = 0; + foreach(lc, values) + { + option_value *val = lfirst(lc); - foreach(lc, relopts->validators) - ((relopts_validator) lfirst(lc)) (opts, vals, noptions); + memcpy(&(val_array[i]), val, sizeof(option_value)); + i++; + } - if (elems) - pfree(elems); + foreach(lc, relopts->validators) + ((relopts_validator) lfirst(lc)) (opts, val_array, val_count); + pfree(val_array); + } return opts; -} -/* - * Option parser for partitioned tables - */ -bytea * -partitioned_table_reloptions(Datum reloptions, bool validate) -{ - /* - * There are no options for partitioned tables yet, but this is able to do - * some validation. - */ - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_PARTITIONED, - 0, NULL, 0); } /* - * 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; } /* @@ -2110,33 +735,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 3d15701a01..a6117665d1 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" @@ -28,6 +28,7 @@ #include "utils/builtins.h" #include "utils/index_selfuncs.h" #include "utils/typcache.h" +#include "utils/guc.h" /* @@ -68,7 +69,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; - amroutine->amoptions = ginoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = ginvalidate; @@ -83,6 +83,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gingetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -605,21 +606,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 * @@ -706,3 +692,29 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build) END_CRIT_SECTION(); } + +static options_spec_set *gin_relopt_specset = NULL; + +void * +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 8c6c744ab7..f7300f5c23 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -90,7 +90,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -105,6 +104,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index d4bf0c7563..435c899110 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 "catalog/pg_opclass.h" #include "common/pg_prng.h" #include "storage/indexfsm.h" @@ -917,20 +917,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. * @@ -1065,3 +1051,39 @@ gistGetFakeLSN(Relation rel) return GetFakeLSNForUnloggedRel(); } } + +/* 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; + +void * +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 fd1a7119b6..31a7117bf2 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -87,7 +87,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -102,6 +101,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index fe37bc47cb..b41d36960b 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 "storage/buf_internals.h" @@ -272,19 +272,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 */ @@ -620,3 +607,22 @@ _hash_kill_items(IndexScanDesc scan) else _hash_relbuf(rel, buf); } + +static options_spec_set *hash_relopt_specset = NULL; + +void * +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 06131f23d4..67cdefa712 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -23,6 +23,7 @@ #include "access/relscan.h" #include "access/xlog.h" #include "access/xloginsert.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -126,7 +127,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -141,6 +141,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = btestimateparallelscan; amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1420,3 +1421,35 @@ btcanreturn(Relation index, int attno) { return true; } + +static options_spec_set *bt_relopt_specset = NULL; + +void * +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 ff260c393a..a08004c045 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" #include "access/relscan.h" #include "catalog/catalog.h" #include "commands/progress.h" @@ -2107,23 +2107,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 a171ca8a08..8d566f08a4 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" @@ -74,7 +74,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -89,6 +88,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -733,22 +733,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). @@ -1348,3 +1332,24 @@ spgproperty(Oid index_oid, int attno, return true; } + +static options_spec_set *spgist_relopt_specset = NULL; + +void * +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/access/transam/xact.c b/src/backend/access/transam/xact.c index 47d80b0d25..bd60b55574 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -122,7 +122,7 @@ bool bsysscan = false; * lookups as fast as possible. */ static FullTransactionId XactTopFullTransactionId = {InvalidTransactionId}; -static int nParallelCurrentXids = 0; +static int nParallelCurrentXids = 0; static TransactionId *ParallelCurrentXids; /* diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 9abbb6b555..c91f38819d 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -90,6 +90,7 @@ create_ctas_internal(List *attrList, IntoClause *into) Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; ObjectAddress intoRelationAddr; + List *toastDefList; /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview = (into->viewQuery != NULL); @@ -124,14 +125,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 ea27857bb8..13e50cd08e 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 eac13ac0b7..c5bd18fa68 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -19,6 +19,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" @@ -531,7 +532,7 @@ DefineIndex(Oid relationId, Form_pg_am accessMethodForm; IndexAmRoutine *amRoutine; bool amcanorder; - amoptions_function amoptions; + amreloptspecset_function amreloptspecsetfn; bool partitioned; bool safe_index; Datum reloptions; @@ -847,7 +848,7 @@ DefineIndex(Oid relationId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; pfree(amRoutine); ReleaseSysCache(tuple); @@ -861,10 +862,18 @@ DefineIndex(Oid relationId, /* * 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. @@ -2039,8 +2048,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, palloc0(sizeof(Datum) * indexInfo->ii_NumIndexAttrs); indexInfo->ii_OpclassOptions[attn] = - transformRelOptions((Datum) 0, attribute->opclassopts, - NULL, NULL, false, false); + optionsDefListToTextArray(attribute->opclassopts); } attn++; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 2de0ebacec..9134af219f 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -20,6 +20,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" @@ -672,7 +673,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -820,19 +820,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 */ + char *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 */ + char *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) @@ -4102,7 +4119,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); @@ -4144,7 +4161,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. @@ -4365,9 +4382,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; @@ -8155,12 +8177,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)); @@ -14141,12 +14164,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]; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + char *heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -14166,38 +14190,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), @@ -14207,11 +14249,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; @@ -14286,20 +14332,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 00ca397fe8..608e3c181b 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -364,10 +364,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 @@ -1100,11 +1099,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 cf222fc3e9..2f98df4d35 100644 --- a/src/backend/foreign/foreign.c +++ b/src/backend/foreign/foreign.c @@ -79,7 +79,7 @@ GetForeignDataWrapperExtended(Oid fdwid, bits16 flags) if (isnull) fdw->options = NIL; else - fdw->options = untransformRelOptions(datum); + fdw->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -166,7 +166,7 @@ GetForeignServerExtended(Oid serverid, bits16 flags) if (isnull) server->options = NIL; else - server->options = untransformRelOptions(datum); + server->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -234,7 +234,7 @@ GetUserMapping(Oid userid, Oid serverid) if (isnull) um->options = NIL; else - um->options = untransformRelOptions(datum); + um->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -271,7 +271,7 @@ GetForeignTable(Oid relid) if (isnull) ft->options = NIL; else - ft->options = untransformRelOptions(datum); + ft->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -304,7 +304,7 @@ GetForeignColumnOptions(Oid relid, AttrNumber attnum) if (isnull) options = NIL; else - options = untransformRelOptions(datum); + options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -512,7 +512,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -609,7 +609,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 1a64a52279..cd44d45f66 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1759,7 +1759,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; @@ -1823,7 +1823,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 0e7b7b3138..e78ca3eb35 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1163,6 +1163,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1186,15 +1187,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); @@ -1303,9 +1305,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 9e252a0891..cc3b910379 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" @@ -148,7 +149,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 60e72f9e8b..3a2411e131 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -461,7 +461,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -476,11 +476,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; @@ -491,7 +491,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 5609246c2f..affe4cc2b4 100644 --- a/src/backend/utils/cache/spccache.c +++ b/src/backend/utils/cache/spccache.c @@ -148,7 +148,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/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index 299b9b7621..9271b76988 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -758,7 +758,7 @@ AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); - if (option_value != NULL) + if (option_value !=NULL) { size_t length = strlen(option_value); char *escaped_value = palloc(1 + 2 * length); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 0b89f399f0..e4ed420179 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -136,10 +136,6 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, double *indexCorrelation, double *indexPages); -/* parse index reloptions */ -typedef bytea *(*amoptions_function) (Datum reloptions, - bool validate); - /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, @@ -186,6 +182,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan); /* restore marked scan position */ typedef void (*amrestrpos_function) (IndexScanDesc scan); +/* get catalog of reloptions definitions */ +typedef void *(*amreloptspecset_function) (); + /* * Callback function signatures - for parallel index scans. */ @@ -265,7 +264,6 @@ typedef struct IndexAmRoutine amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; - amoptions_function amoptions; amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; @@ -277,6 +275,7 @@ typedef struct IndexAmRoutine amendscan_function amendscan; ammarkpos_function ammarkpos; /* can be NULL */ amrestrpos_function amrestrpos; /* can be NULL */ + amreloptspecset_function amreloptspecset; /* can be NULL */ /* interface functions to support parallel index scans */ amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ diff --git a/src/include/access/brin.h b/src/include/access/brin.h index 887fb0a553..0aaf118ea5 100644 --- a/src/include/access/brin.h +++ b/src/include/access/brin.h @@ -36,6 +36,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 2518660927..a7dbb8a5f8 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -14,6 +14,7 @@ #include "access/amapi.h" #include "storage/bufpage.h" #include "utils/typcache.h" +#include "access/options.h" /* @@ -108,6 +109,7 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bytea *brinoptions(Datum reloptions, bool validate); +extern void *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 2935d2f353..e69fd6fd52 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -108,6 +108,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 void *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 240131ef71..7e586061e7 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 */ @@ -478,7 +480,7 @@ extern void gistadjustmembers(Oid opfamilyoid, #define GIST_MIN_FILLFACTOR 10 #define GIST_DEFAULT_FILLFACTOR 90 -extern bytea *gistoptions(Datum reloptions, bool validate); +extern void *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 da372841c4..12ff5ffcbb 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, @@ -472,6 +471,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 void *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 93f8267b48..3979cb4e47 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1254,7 +1254,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 void *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 0000000000..1174303979 --- /dev/null +++ b/src/include/access/options.h @@ -0,0 +1,262 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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; + +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 catalog + * 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 forbid_realloc; /* If number of items of the spec_set were + * strictly set to certain value do no allow + * adding more idems */ + 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 *namespace; /* 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 *namespace; + 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 *namespace, + 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, + char **allowed_namespaces); +extern List *optionsDefListFilterNamespaces(List *defList, + const char *namespace); +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 f740513303..1f4b6d9c28 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-2022, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -22,178 +17,40 @@ #include "access/amapi.h" #include "access/htup.h" #include "access/tupdesc.h" +#include "access/options.h" #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 *opts, Size relopt_struct_size); extern void register_reloptions_validator(local_relopts *opts, @@ -210,7 +67,7 @@ extern void add_local_real_reloption(local_relopts *opts, 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 *opts, const char *name, @@ -219,29 +76,17 @@ extern void add_local_string_reloption(local_relopts *opts, const char *name, validate_string_relopt validator, fill_string_relopt filler, int offset); -extern Datum transformRelOptions(Datum oldOptions, List *defList, - const char *namspace, char *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 5ad4f85940..99664a4114 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 eb56b1c6b8..d3ffe645c3 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -529,6 +529,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 void *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 5d4037f26e..1086723b00 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -34,7 +34,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 22578b6246..17a734e5b1 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,7 +14,7 @@ #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" @@ -25,12 +25,6 @@ PG_MODULE_MAGIC; void _PG_init(void); -/* parse table for fillRelOptions */ -relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -relopt_kind di_relopt_kind; - typedef enum DummyAmEnum { DUMMY_AM_ENUM_ONE, @@ -49,7 +43,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -63,77 +57,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; +void *digetreloptspecset(void); + +void * +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. */ @@ -218,19 +217,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. */ @@ -309,7 +295,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = divacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -323,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/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index b6aef6f654..72fee4176b 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -164,7 +164,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 @@ -183,6 +183,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 4252b0202f..fadce3384d 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -105,6 +105,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 -- diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 4fb746930a..1dbf3cb5c7 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3103,9 +3103,9 @@ amgettuple_function aminitparallelscan_function aminsert_function ammarkpos_function -amoptions_function amparallelrescan_function amproperty_function +amreloptspecset_function amrescan_function amrestrpos_function amvacuumcleanup_function @@ -3259,6 +3259,7 @@ file_type_t filehash_hash filehash_iterator filemap_t +fill_string_option fill_string_relopt finalize_primnode_context find_dependent_phvs_context @@ -3386,7 +3387,6 @@ libpq_source line_t lineno_t list_sort_comparator -local_relopt local_relopts local_source locale_t @@ -3444,6 +3444,18 @@ oidvector on_dsm_detach_callback on_exit_nicely_callback openssl_tls_init_hook_typ +opt_enum_elt_def +option_spec_basic +option_spec_bool +option_spec_enum +option_spec_int +option_spec_real +option_spec_string +option_type +option_value +option_value_postvalidate +option_value_status +options_spec_set ossl_EVP_cipher_func other output_type @@ -3556,6 +3568,7 @@ pointer polymorphic_actuals pos_trgm post_parse_analyze_hook_type +postprocess_bytea_options_function postprocess_result_function pqbool pqsigfunc @@ -3610,16 +3623,6 @@ registered_buffer regmatch_t regoff_t regproc -relopt_bool -relopt_enum -relopt_enum_elt_def -relopt_gen -relopt_int -relopt_kind -relopt_parse_elt -relopt_real -relopt_string -relopt_type relopt_value relopts_validator remoteConn @@ -3766,6 +3769,7 @@ uuid_sortsupport_state uuid_t va_list vacuumingOptions +validate_string_option validate_string_relopt varatt_expanded varattrib_1b -- 2.30.2 --nextPart5904915.V09D27m8HI-- --nextPart3198980.n4Qna3SLz4 Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmKDkJMACgkQPMbfuIHA GphXvwgAiu3rdj6Ne/xkEIJHAUD9XdfE7wNvDweP8a99PDkFGSGbNr/i8xEtKYTF JuthFqGID4P8vW6wUCPzdKR5D9YkYSRb56h3vlSt5uIqaq2iVGWGttXil/5vTvJC 36QDBVCpY1ATBjwA5qykkVLacs4Fb61EA/VrDZ5/IyvDzEcs3QyQqCsGvtVKV8gR ptDOMezKfPg8O2chMUPAaYTEwbykPDd1V8qs7VJLUu3E8/A/UITSEw3yGmCIjw95 oHygOiBHyIWOALiHFh9s/lpGaVNkXjqDqxAnpneCj9kCwwBKDHKKikoDT5GE2Ia1 celaxTl73ZU9FLT+fmxfGc4RDbx/yg== =/u4T -----END PGP SIGNATURE----- --nextPart3198980.n4Qna3SLz4-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH] New options engine @ 2022-05-16 14:13 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2022-05-16 14:13 UTC (permalink / raw) 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 | 111 +- contrib/bloom/expected/bloom.out | 3 + contrib/bloom/sql/bloom.sql | 3 + 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 +- src/backend/access/brin/brin.c | 47 +- src/backend/access/common/Makefile | 1 + src/backend/access/common/options.c | 1364 ++++++++++ src/backend/access/common/reloptions.c | 2317 ++++------------- 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 | 19 +- src/backend/access/spgist/spgutils.c | 41 +- src/backend/access/transam/xact.c | 2 +- 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/bin/pg_basebackup/streamutil.c | 2 +- src/include/access/amapi.h | 9 +- src/include/access/brin.h | 2 + src/include/access/brin_internal.h | 2 + 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 | 262 ++ src/include/access/reloptions.h | 205 +- 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 +- src/test/regress/expected/reloptions.out | 13 +- src/test/regress/sql/reloptions.sql | 7 + src/tools/pgindent/typedefs.list | 28 +- 49 files changed, 2636 insertions(+), 2379 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 9966f3f46e..9a009bdbb3 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" @@ -207,7 +208,8 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *bloptions(Datum reloptions, bool validate); +extern void *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 a434cf93ef..6587a721c4 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 "catalog/index.h" #include "commands/vacuum.h" @@ -34,52 +34,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->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; - amroutine->amoptions = bloptions; + amroutine->amreloptspecset = blrelopt_specset; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = blvalidate; @@ -154,6 +114,24 @@ blhandler(PG_FUNCTION_ARGS) PG_RETURN_POINTER(amroutine); } +void +blReloptionPostprocess(void *data, bool validate) +{ + BloomOptions *opts = (BloomOptions *) data; + int i; + + if (validate) + for (i = 0; i < INDEX_MAX_KEYS; i++) + if (opts->bitSize[i] >= opts->bloomLength) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("col%i should not be grater than length", i))); + + /* Convert signature length from # of bits to # to words, rounding up */ + opts->bloomLength = (opts->bloomLength + SIGNWORDBITS - 1) / SIGNWORDBITS; +} + + /* * Fill BloomState structure for particular index. */ @@ -474,24 +452,37 @@ BloomInitMetapage(Relation index) UnlockReleaseBuffer(metaBuffer); } -/* - * Parse reloptions for bloom index, producing a BloomOptions struct. - */ -bytea * -bloptions(Datum reloptions, bool validate) +void * +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/bloom/expected/bloom.out b/contrib/bloom/expected/bloom.out index dae12a7d3e..e79456dbce 100644 --- a/contrib/bloom/expected/bloom.out +++ b/contrib/bloom/expected/bloom.out @@ -228,3 +228,6 @@ CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=0); ERROR: value 0 out of bounds for option "length" CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (col1=0); ERROR: value 0 out of bounds for option "col1" +-- check post_validate for colN<lengh +CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=10,col1=11); +ERROR: col0 should not be grater than length diff --git a/contrib/bloom/sql/bloom.sql b/contrib/bloom/sql/bloom.sql index 4733e1e705..0bfc767faf 100644 --- a/contrib/bloom/sql/bloom.sql +++ b/contrib/bloom/sql/bloom.sql @@ -93,3 +93,6 @@ SELECT reloptions FROM pg_class WHERE oid = 'bloomidx'::regclass; \set VERBOSITY terse CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=0); CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (col1=0); + +-- check post_validate for colN<lengh +CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=10,col1=11); diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c index a561d1d652..2aafe2df83 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -1977,7 +1977,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 4773cadec0..1de15ce09b 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -195,7 +195,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 572591a558..c56457140f 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -73,7 +73,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 e89dc74a5e..af46f0a3ee 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/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 52f171772d..a1eb51bcd6 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" @@ -40,7 +39,6 @@ #include "utils/memutils.h" #include "utils/rel.h" - /* * We use a BrinBuildState during initial construction of a BRIN index. * The running state is kept in a BrinMemTuple. @@ -120,7 +118,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -135,6 +132,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -963,23 +961,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. @@ -1785,3 +1766,29 @@ check_null_keys(BrinValues *bval, ScanKey *nullkeys, int nnullkeys) return true; } + +static options_spec_set *brin_relopt_specset = NULL; + +void * +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 b9aff0ccfd..78c9c5a8d2 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/options.c b/src/backend/access/common/options.c new file mode 100644 index 0000000000..499e98c274 --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1364 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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 therm "specification" instead of "definition" because therm + * "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 Vale 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 vales. 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 + offest. + */ + +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 Option 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. + * + * size_of_bytea - 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 *namespace, 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 (namespace) + { + spec_set->namespace = palloc(strlen(namespace) + 1); + strcpy(spec_set->namespace, namespace); + } + else + spec_set->namespace = NULL; + if (num_items_expected > 0) + { + spec_set->num_allocated = num_items_expected; + spec_set->forbid_realloc = true; + spec_set->definitions = palloc( + spec_set->num_allocated * sizeof(option_spec_basic *)); + } + else + { + spec_set->num_allocated = 0; + spec_set->forbid_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", 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 objec 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->forbid_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->namespace = palloc(strlen(def->defnamespace) + 1); + strcpy(option_dst->namespace, def->defnamespace); + } + else + option_dst->namespace = 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(array, TEXTOID, -1, false, 'i', + &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 i; + 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 (i = 0; i < text_len; i = i + pg_mblen(text_str)) + { + if (text_str[i] == '=') + { + name_len = i; + 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->namespace = 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_namespaces array. Items without namespace are also accepted + * + * Used while validation of syntax parser output. Error is thrown if unproper + * namespace is found. + */ +void +optionsDefListValdateNamespaces(List *defList, char **allowed_namespaces) +{ + 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_namespaces[i]) + { + if (strcmp(def->defnamespace, + allowed_namespaces[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 "namespace" namespace. If "namespace" + * is NULL, only namespaseless options are returned + */ +List * +optionsDefListFilterNamespaces(List *defList, const char *namespace) +{ + ListCell *cell; + List *result = NIL; + + foreach(cell, defList) + { + DefElem *def = (DefElem *) lfirst(cell); + + if ((!namespace && !def->defnamespace) || + (namespace && def->defnamespace && + strcmp(namespace, 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(array, TEXTOID, -1, false, 'i', + &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->namespace && (!spec_set->namespace || + strcmp(spec_set->namespace, option->namespace) != 0)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter namespace \"%s\"", + option->namespace))); + + 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->namespace) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter \"%s.%s\"", + option->namespace, 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", 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", + 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 = optionsValuesToBytea(new_values, spec_set); + + spec_set->postprocess_fun(data, true); + 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 24211807ca..b01462d0cd 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-2022, 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" @@ -36,6 +33,7 @@ #include "utils/guc.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "storage/bufmgr.h" /* * Contents of pg_class.reloptions @@ -93,389 +91,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_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}, @@ -489,17 +106,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}, @@ -507,225 +115,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_for_toast); +void oid_postvalidate(option_value *value); /* * init_local_reloptions @@ -735,9 +127,8 @@ add_reloption(relopt_gen *newoption) void init_local_reloptions(local_relopts *opts, Size relopt_struct_size) { - opts->options = NIL; opts->validators = NIL; - opts->relopt_struct_size = relopt_struct_size; + opts->spec_set = allocateOptionsSpecSet(NULL, relopt_struct_size, true, 0); } /* @@ -751,112 +142,6 @@ register_reloptions_validator(local_relopts *opts, relopts_validator validator) opts->validators = lappend(opts->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 @@ -867,504 +152,71 @@ 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); -} - -/* - * add_local_int_reloption - * Add a new local integer reloption - * - * 'offset' is offset of int-typed field. - */ -void -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); -} - -/* - * add_local_real_reloption - * Add a new local float reloption - * - * '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; + optionsSpecSetAddBool(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val); } - -/* - * 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; - } - - return newoption; -} - -/* - * add_string_reloption - * Add a new string 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. - */ -void -add_string_reloption(bits32 kinds, const char *name, const char *desc, - const char *default_val, validate_string_relopt validator, - LOCKMODE lockmode) -{ - relopt_string *newoption = init_string_reloption(kinds, name, desc, - default_val, - validator, NULL, - lockmode); - - add_reloption((relopt_gen *) newoption); -} - -/* - * add_local_string_reloption - * Add a new local string reloption - * - * 'offset' is offset of int-typed field that will store offset of string value - * in the resulting bytea structure. - */ -void -add_local_string_reloption(local_relopts *relopts, const char *name, - const char *desc, const char *default_val, - 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, - char *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(array, TEXTOID, -1, false, TYPALIGN_INT, - &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(). + * add_local_int_reloption + * Add a new local integer reloption + * + * 'offset' is offset of int-typed field. */ -List * -untransformRelOptions(Datum options) +void +add_local_int_reloption(local_relopts *relopts, const char *name, + const char *desc, int default_val, int min_val, + int max_val, int offset) { - 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); + optionsSpecSetAddInt(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); +} - deconstruct_array(array, TEXTOID, -1, false, TYPALIGN_INT, - &optiondatums, NULL, &noptions); +/* + * add_local_real_reloption + * Add a new local float reloption + * + * '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) +{ + optionsSpecSetAddReal(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); - 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)); - } +/* + * 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, opt_enum_elt_def *members, + int default_val, const char *detailmsg, int offset) +{ + optionsSpecSetAddEnum(relopts->spec_set, name, desc, NoLock, offset, NULL, + members, default_val, detailmsg); +} - return result; +/* + * add_local_string_reloption + * Add a new local string reloption + * + * 'offset' is offset of int-typed field that will store offset of string value + * in the resulting bytea structure. + */ +void +add_local_string_reloption(local_relopts *relopts, const char *name, + const char *desc, const char *default_val, + validate_string_relopt validator, + fill_string_relopt filler, int offset) +{ + optionsSpecSetAddString(relopts->spec_set, name, desc, NoLock, offset, + NULL, default_val, validator, filler); } /* @@ -1381,12 +233,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, @@ -1400,545 +253,308 @@ 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(array, TEXTOID, -1, false, TYPALIGN_INT, - &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. - * - * options is a reloption text array as constructed by transformRelOptions. - * kind specifies the family of options to be processed. - * - * 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. - * - * 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). - * - * 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. + * 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 relopt_value * -parseRelOptions(Datum options, bool validate, relopt_kind kind, - int *numrelopts) +static void +toast_options_postprocess(void *data, bool validate) { - 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++; + StdRdOptions *toast_options = (StdRdOptions *) data; - 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++; - } - } - } - - /* Done if no options */ - if (PointerIsValid(DatumGetPointer(options))) - parseRelOptionsInternal(options, validate, reloptions, numoptions); - - *numrelopts = numoptions; - return reloptions; + toast_options->fillfactor = 100; + toast_options->autovacuum.analyze_threshold = -1; + toast_options->autovacuum.analyze_scale_factor = -1; } -/* 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; - - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); - - values[i].gen = opt->option; - values[i].isset = false; - - i++; - } - - if (options != (Datum) 0) - parseRelOptionsInternal(options, validate, values, nopts); - - return values; -} /* - * Subroutine for parseRelOptions, to parse and validate a single option's - * value + * 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. + * + * Autovacuum related parameters can be set at ShareUpdateExclusiveLock + * since they are only used by the AV procs and don't change anything + * currently executing. + * + * Fillfactor can be set because it applies only to subsequent changes made to + * data blocks, as documented in heapio.c + * + * 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. + * + * 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. */ -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; +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) + stdrd_relopt_spec_set->postprocess_fun = toast_options_postprocess; + + 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); + + 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); } - if (parsed) - option->isset = true; - if (!nofree) - pfree(value); -} - -/* - * 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). - */ -static void * -allocateReloptStruct(Size base, relopt_value *options, int numoptions) -{ - Size size = base; - int i; - - for (i = 0; i < numoptions; i++) - { - relopt_value *optval = &options[i]; - - if (optval->gen->type == RELOPT_TYPE_STRING) - { - relopt_string *optstr = (relopt_string *) optval->gen; + 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\".")); - if (optstr->fill_cb) - { - const char *val = optval->isset ? optval->values.string_val : - optstr->default_isnull ? NULL : optstr->default_val; + 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); - size += optstr->fill_cb(val, NULL); - } - else - size += GET_STRING_RELOPTION_LEN(*optval) + 1; - } - } + 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 palloc0(size); + return stdrd_relopt_spec_set; } -/* - * 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) -{ - int i; - int offset = basesize; - for (i = 0; i < numoptions; i++) - { - int j; - bool found = false; +static options_spec_set *heap_relopt_spec_set = NULL; - 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); +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; } +static options_spec_set *toast_relopt_spec_set = NULL; -/* - * Option parser for anything that uses StdRdOptions. - */ -bytea * -default_reloptions(Datum reloptions, bool validate, relopt_kind kind) +options_spec_set * +get_toast_relopt_spec_set(void) { - 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_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 (toast_relopt_spec_set) + return toast_relopt_spec_set; + toast_relopt_spec_set = get_stdrd_relopt_spec_set(false); + return toast_relopt_spec_set; } -/* - * 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); +static options_spec_set *partitioned_relopt_spec_set = NULL; - /* if none set, we're done */ - if (numoptions == 0) - { - Assert(options == NULL); - return 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); - - pfree(options); +options_spec_set * +get_partitioned_relopt_spec_set(void) +{ + if (partitioned_relopt_spec_set) + return partitioned_relopt_spec_set; + partitioned_relopt_spec_set = allocateOptionsSpecSet( + NULL, sizeof(StdRdOptions), false, 0); + /* No options for now, so spec set is empty */ - return rdopts; + return partitioned_relopt_spec_set; } /* @@ -1949,158 +565,167 @@ 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; - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); + 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 + */ - elems[i].optname = opt->option->name; - elems[i].opttype = opt->option->type; - elems[i].offset = opt->offset; + if (validate && relopts->validators) + { + int val_count = 0; + int i; + option_value *val_array; - i++; - } + foreach(lc, values) + val_count++; + val_array = palloc(sizeof(option_value) * val_count); - vals = parseLocalRelOptions(relopts, options, validate); - opts = allocateReloptStruct(relopts->relopt_struct_size, vals, noptions); - fillRelOptions(opts, relopts->relopt_struct_size, vals, noptions, validate, - elems, noptions); + i = 0; + foreach(lc, values) + { + option_value *val = lfirst(lc); - foreach(lc, relopts->validators) - ((relopts_validator) lfirst(lc)) (opts, vals, noptions); + memcpy(&(val_array[i]), val, sizeof(option_value)); + i++; + } - if (elems) - pfree(elems); + foreach(lc, relopts->validators) + ((relopts_validator) lfirst(lc)) (opts, val_array, val_count); + pfree(val_array); + } return opts; -} -/* - * Option parser for partitioned tables - */ -bytea * -partitioned_table_reloptions(Datum reloptions, bool validate) -{ - /* - * There are no options for partitioned tables yet, but this is able to do - * some validation. - */ - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_PARTITIONED, - 0, NULL, 0); } /* - * 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; } /* @@ -2110,33 +735,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 3d15701a01..a6117665d1 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" @@ -28,6 +28,7 @@ #include "utils/builtins.h" #include "utils/index_selfuncs.h" #include "utils/typcache.h" +#include "utils/guc.h" /* @@ -68,7 +69,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; - amroutine->amoptions = ginoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = ginvalidate; @@ -83,6 +83,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gingetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -605,21 +606,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 * @@ -706,3 +692,29 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build) END_CRIT_SECTION(); } + +static options_spec_set *gin_relopt_specset = NULL; + +void * +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 8c6c744ab7..f7300f5c23 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -90,7 +90,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -105,6 +104,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index d4bf0c7563..435c899110 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 "catalog/pg_opclass.h" #include "common/pg_prng.h" #include "storage/indexfsm.h" @@ -917,20 +917,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. * @@ -1065,3 +1051,39 @@ gistGetFakeLSN(Relation rel) return GetFakeLSNForUnloggedRel(); } } + +/* 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; + +void * +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 fd1a7119b6..31a7117bf2 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -87,7 +87,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -102,6 +101,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index fe37bc47cb..b41d36960b 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 "storage/buf_internals.h" @@ -272,19 +272,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 */ @@ -620,3 +607,22 @@ _hash_kill_items(IndexScanDesc scan) else _hash_relbuf(rel, buf); } + +static options_spec_set *hash_relopt_specset = NULL; + +void * +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 06131f23d4..67cdefa712 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -23,6 +23,7 @@ #include "access/relscan.h" #include "access/xlog.h" #include "access/xloginsert.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -126,7 +127,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -141,6 +141,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = btestimateparallelscan; amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1420,3 +1421,35 @@ btcanreturn(Relation index, int attno) { return true; } + +static options_spec_set *bt_relopt_specset = NULL; + +void * +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 ff260c393a..a08004c045 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" #include "access/relscan.h" #include "catalog/catalog.h" #include "commands/progress.h" @@ -2107,23 +2107,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 a171ca8a08..8d566f08a4 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" @@ -74,7 +74,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -89,6 +88,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -733,22 +733,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). @@ -1348,3 +1332,24 @@ spgproperty(Oid index_oid, int attno, return true; } + +static options_spec_set *spgist_relopt_specset = NULL; + +void * +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/access/transam/xact.c b/src/backend/access/transam/xact.c index 47d80b0d25..bd60b55574 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -122,7 +122,7 @@ bool bsysscan = false; * lookups as fast as possible. */ static FullTransactionId XactTopFullTransactionId = {InvalidTransactionId}; -static int nParallelCurrentXids = 0; +static int nParallelCurrentXids = 0; static TransactionId *ParallelCurrentXids; /* diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 9abbb6b555..c91f38819d 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -90,6 +90,7 @@ create_ctas_internal(List *attrList, IntoClause *into) Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; ObjectAddress intoRelationAddr; + List *toastDefList; /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview = (into->viewQuery != NULL); @@ -124,14 +125,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 ea27857bb8..13e50cd08e 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 eac13ac0b7..c5bd18fa68 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -19,6 +19,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" @@ -531,7 +532,7 @@ DefineIndex(Oid relationId, Form_pg_am accessMethodForm; IndexAmRoutine *amRoutine; bool amcanorder; - amoptions_function amoptions; + amreloptspecset_function amreloptspecsetfn; bool partitioned; bool safe_index; Datum reloptions; @@ -847,7 +848,7 @@ DefineIndex(Oid relationId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; pfree(amRoutine); ReleaseSysCache(tuple); @@ -861,10 +862,18 @@ DefineIndex(Oid relationId, /* * 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. @@ -2039,8 +2048,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, palloc0(sizeof(Datum) * indexInfo->ii_NumIndexAttrs); indexInfo->ii_OpclassOptions[attn] = - transformRelOptions((Datum) 0, attribute->opclassopts, - NULL, NULL, false, false); + optionsDefListToTextArray(attribute->opclassopts); } attn++; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 2de0ebacec..9134af219f 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -20,6 +20,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" @@ -672,7 +673,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -820,19 +820,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 */ + char *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 */ + char *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) @@ -4102,7 +4119,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); @@ -4144,7 +4161,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. @@ -4365,9 +4382,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; @@ -8155,12 +8177,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)); @@ -14141,12 +14164,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]; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + char *heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -14166,38 +14190,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), @@ -14207,11 +14249,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; @@ -14286,20 +14332,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 00ca397fe8..608e3c181b 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -364,10 +364,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 @@ -1100,11 +1099,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 cf222fc3e9..2f98df4d35 100644 --- a/src/backend/foreign/foreign.c +++ b/src/backend/foreign/foreign.c @@ -79,7 +79,7 @@ GetForeignDataWrapperExtended(Oid fdwid, bits16 flags) if (isnull) fdw->options = NIL; else - fdw->options = untransformRelOptions(datum); + fdw->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -166,7 +166,7 @@ GetForeignServerExtended(Oid serverid, bits16 flags) if (isnull) server->options = NIL; else - server->options = untransformRelOptions(datum); + server->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -234,7 +234,7 @@ GetUserMapping(Oid userid, Oid serverid) if (isnull) um->options = NIL; else - um->options = untransformRelOptions(datum); + um->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -271,7 +271,7 @@ GetForeignTable(Oid relid) if (isnull) ft->options = NIL; else - ft->options = untransformRelOptions(datum); + ft->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -304,7 +304,7 @@ GetForeignColumnOptions(Oid relid, AttrNumber attnum) if (isnull) options = NIL; else - options = untransformRelOptions(datum); + options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -512,7 +512,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -609,7 +609,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 1a64a52279..cd44d45f66 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1759,7 +1759,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; @@ -1823,7 +1823,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 0e7b7b3138..e78ca3eb35 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1163,6 +1163,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1186,15 +1187,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); @@ -1303,9 +1305,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 9e252a0891..cc3b910379 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" @@ -148,7 +149,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 60e72f9e8b..3a2411e131 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -461,7 +461,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -476,11 +476,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; @@ -491,7 +491,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 5609246c2f..affe4cc2b4 100644 --- a/src/backend/utils/cache/spccache.c +++ b/src/backend/utils/cache/spccache.c @@ -148,7 +148,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/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index 299b9b7621..9271b76988 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -758,7 +758,7 @@ AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); - if (option_value != NULL) + if (option_value !=NULL) { size_t length = strlen(option_value); char *escaped_value = palloc(1 + 2 * length); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 0b89f399f0..e4ed420179 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -136,10 +136,6 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, double *indexCorrelation, double *indexPages); -/* parse index reloptions */ -typedef bytea *(*amoptions_function) (Datum reloptions, - bool validate); - /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, @@ -186,6 +182,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan); /* restore marked scan position */ typedef void (*amrestrpos_function) (IndexScanDesc scan); +/* get catalog of reloptions definitions */ +typedef void *(*amreloptspecset_function) (); + /* * Callback function signatures - for parallel index scans. */ @@ -265,7 +264,6 @@ typedef struct IndexAmRoutine amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; - amoptions_function amoptions; amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; @@ -277,6 +275,7 @@ typedef struct IndexAmRoutine amendscan_function amendscan; ammarkpos_function ammarkpos; /* can be NULL */ amrestrpos_function amrestrpos; /* can be NULL */ + amreloptspecset_function amreloptspecset; /* can be NULL */ /* interface functions to support parallel index scans */ amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ diff --git a/src/include/access/brin.h b/src/include/access/brin.h index 887fb0a553..0aaf118ea5 100644 --- a/src/include/access/brin.h +++ b/src/include/access/brin.h @@ -36,6 +36,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 2518660927..a7dbb8a5f8 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -14,6 +14,7 @@ #include "access/amapi.h" #include "storage/bufpage.h" #include "utils/typcache.h" +#include "access/options.h" /* @@ -108,6 +109,7 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bytea *brinoptions(Datum reloptions, bool validate); +extern void *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 2935d2f353..e69fd6fd52 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -108,6 +108,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 void *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 240131ef71..7e586061e7 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 */ @@ -478,7 +480,7 @@ extern void gistadjustmembers(Oid opfamilyoid, #define GIST_MIN_FILLFACTOR 10 #define GIST_DEFAULT_FILLFACTOR 90 -extern bytea *gistoptions(Datum reloptions, bool validate); +extern void *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 da372841c4..12ff5ffcbb 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, @@ -472,6 +471,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 void *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 93f8267b48..3979cb4e47 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1254,7 +1254,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 void *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 0000000000..1174303979 --- /dev/null +++ b/src/include/access/options.h @@ -0,0 +1,262 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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; + +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 catalog + * 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 forbid_realloc; /* If number of items of the spec_set were + * strictly set to certain value do no allow + * adding more idems */ + 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 *namespace; /* 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 *namespace; + 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 *namespace, + 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, + char **allowed_namespaces); +extern List *optionsDefListFilterNamespaces(List *defList, + const char *namespace); +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 f740513303..1f4b6d9c28 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-2022, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -22,178 +17,40 @@ #include "access/amapi.h" #include "access/htup.h" #include "access/tupdesc.h" +#include "access/options.h" #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 *opts, Size relopt_struct_size); extern void register_reloptions_validator(local_relopts *opts, @@ -210,7 +67,7 @@ extern void add_local_real_reloption(local_relopts *opts, 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 *opts, const char *name, @@ -219,29 +76,17 @@ extern void add_local_string_reloption(local_relopts *opts, const char *name, validate_string_relopt validator, fill_string_relopt filler, int offset); -extern Datum transformRelOptions(Datum oldOptions, List *defList, - const char *namspace, char *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 5ad4f85940..99664a4114 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 eb56b1c6b8..d3ffe645c3 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -529,6 +529,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 void *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 5d4037f26e..1086723b00 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -34,7 +34,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 22578b6246..17a734e5b1 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,7 +14,7 @@ #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" @@ -25,12 +25,6 @@ PG_MODULE_MAGIC; void _PG_init(void); -/* parse table for fillRelOptions */ -relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -relopt_kind di_relopt_kind; - typedef enum DummyAmEnum { DUMMY_AM_ENUM_ONE, @@ -49,7 +43,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -63,77 +57,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; +void *digetreloptspecset(void); + +void * +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. */ @@ -218,19 +217,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. */ @@ -309,7 +295,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = divacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -323,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/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index b6aef6f654..72fee4176b 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -164,7 +164,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 @@ -183,6 +183,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 4252b0202f..fadce3384d 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -105,6 +105,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 -- diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 4fb746930a..1dbf3cb5c7 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3103,9 +3103,9 @@ amgettuple_function aminitparallelscan_function aminsert_function ammarkpos_function -amoptions_function amparallelrescan_function amproperty_function +amreloptspecset_function amrescan_function amrestrpos_function amvacuumcleanup_function @@ -3259,6 +3259,7 @@ file_type_t filehash_hash filehash_iterator filemap_t +fill_string_option fill_string_relopt finalize_primnode_context find_dependent_phvs_context @@ -3386,7 +3387,6 @@ libpq_source line_t lineno_t list_sort_comparator -local_relopt local_relopts local_source locale_t @@ -3444,6 +3444,18 @@ oidvector on_dsm_detach_callback on_exit_nicely_callback openssl_tls_init_hook_typ +opt_enum_elt_def +option_spec_basic +option_spec_bool +option_spec_enum +option_spec_int +option_spec_real +option_spec_string +option_type +option_value +option_value_postvalidate +option_value_status +options_spec_set ossl_EVP_cipher_func other output_type @@ -3556,6 +3568,7 @@ pointer polymorphic_actuals pos_trgm post_parse_analyze_hook_type +postprocess_bytea_options_function postprocess_result_function pqbool pqsigfunc @@ -3610,16 +3623,6 @@ registered_buffer regmatch_t regoff_t regproc -relopt_bool -relopt_enum -relopt_enum_elt_def -relopt_gen -relopt_int -relopt_kind -relopt_parse_elt -relopt_real -relopt_string -relopt_type relopt_value relopts_validator remoteConn @@ -3766,6 +3769,7 @@ uuid_sortsupport_state uuid_t va_list vacuumingOptions +validate_string_option validate_string_relopt varatt_expanded varattrib_1b -- 2.30.2 --nextPart5904915.V09D27m8HI-- --nextPart3198980.n4Qna3SLz4 Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmKDkJMACgkQPMbfuIHA GphXvwgAiu3rdj6Ne/xkEIJHAUD9XdfE7wNvDweP8a99PDkFGSGbNr/i8xEtKYTF JuthFqGID4P8vW6wUCPzdKR5D9YkYSRb56h3vlSt5uIqaq2iVGWGttXil/5vTvJC 36QDBVCpY1ATBjwA5qykkVLacs4Fb61EA/VrDZ5/IyvDzEcs3QyQqCsGvtVKV8gR ptDOMezKfPg8O2chMUPAaYTEwbykPDd1V8qs7VJLUu3E8/A/UITSEw3yGmCIjw95 oHygOiBHyIWOALiHFh9s/lpGaVNkXjqDqxAnpneCj9kCwwBKDHKKikoDT5GE2Ia1 celaxTl73ZU9FLT+fmxfGc4RDbx/yg== =/u4T -----END PGP SIGNATURE----- --nextPart3198980.n4Qna3SLz4-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH] New options engine @ 2022-05-18 15:25 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2022-05-18 15:25 UTC (permalink / raw) 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 +- src/backend/access/brin/brin.c | 47 +- src/backend/access/common/Makefile | 1 + src/backend/access/common/options.c | 1364 ++++++++++ src/backend/access/common/reloptions.c | 2317 ++++------------- 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 | 19 +- src/backend/access/spgist/spgutils.c | 41 +- src/backend/access/transam/xact.c | 2 +- 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/bin/pg_basebackup/streamutil.c | 2 +- src/include/access/amapi.h | 9 +- src/include/access/brin.h | 2 + src/include/access/brin_internal.h | 2 + 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 | 262 ++ src/include/access/reloptions.h | 205 +- 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 +- src/test/regress/expected/reloptions.out | 13 +- src/test/regress/sql/reloptions.sql | 7 + src/tools/pgindent/typedefs.list | 28 +- 47 files changed, 2621 insertions(+), 2379 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 9966f3f46e..9a009bdbb3 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" @@ -207,7 +208,8 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *bloptions(Datum reloptions, bool validate); +extern void *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 a434cf93ef..522da7dc9f 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 "catalog/index.h" #include "commands/vacuum.h" @@ -34,52 +34,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->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; - amroutine->amoptions = bloptions; + amroutine->amreloptspecset = blrelopt_specset; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = blvalidate; @@ -154,6 +114,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. */ @@ -474,24 +443,37 @@ BloomInitMetapage(Relation index) UnlockReleaseBuffer(metaBuffer); } -/* - * Parse reloptions for bloom index, producing a BloomOptions struct. - */ -bytea * -bloptions(Datum reloptions, bool validate) +void * +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 a561d1d652..2aafe2df83 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -1977,7 +1977,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 4773cadec0..1de15ce09b 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -195,7 +195,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 572591a558..c56457140f 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -73,7 +73,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 e89dc74a5e..af46f0a3ee 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/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 52f171772d..a1eb51bcd6 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" @@ -40,7 +39,6 @@ #include "utils/memutils.h" #include "utils/rel.h" - /* * We use a BrinBuildState during initial construction of a BRIN index. * The running state is kept in a BrinMemTuple. @@ -120,7 +118,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -135,6 +132,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -963,23 +961,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. @@ -1785,3 +1766,29 @@ check_null_keys(BrinValues *bval, ScanKey *nullkeys, int nnullkeys) return true; } + +static options_spec_set *brin_relopt_specset = NULL; + +void * +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 b9aff0ccfd..78c9c5a8d2 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/options.c b/src/backend/access/common/options.c new file mode 100644 index 0000000000..efc377285e --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1364 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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 therm "specification" instead of "definition" because therm + * "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 Vale 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 vales. 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 + offest. + */ + +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 Option 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. + * + * size_of_bytea - 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 *namespace, 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 (namespace) + { + spec_set->namespace = palloc(strlen(namespace) + 1); + strcpy(spec_set->namespace, namespace); + } + else + spec_set->namespace = 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", 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 objec 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->namespace = palloc(strlen(def->defnamespace) + 1); + strcpy(option_dst->namespace, def->defnamespace); + } + else + option_dst->namespace = 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(array, TEXTOID, -1, false, 'i', + &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 i; + 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 (i = 0; i < text_len; i = i + pg_mblen(text_str)) + { + if (text_str[i] == '=') + { + name_len = i; + 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->namespace = 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_namespaces array. Items without namespace are also accepted + * + * Used while validation of syntax parser output. Error is thrown if unproper + * namespace is found. + */ +void +optionsDefListValdateNamespaces(List *defList, char **allowed_namespaces) +{ + 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_namespaces[i]) + { + if (strcmp(def->defnamespace, + allowed_namespaces[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 "namespace" namespace. If "namespace" + * is NULL, only namespaseless options are returned + */ +List * +optionsDefListFilterNamespaces(List *defList, const char *namespace) +{ + ListCell *cell; + List *result = NIL; + + foreach(cell, defList) + { + DefElem *def = (DefElem *) lfirst(cell); + + if ((!namespace && !def->defnamespace) || + (namespace && def->defnamespace && + strcmp(namespace, 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(array, TEXTOID, -1, false, 'i', + &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->namespace && (!spec_set->namespace || + strcmp(spec_set->namespace, option->namespace) != 0)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter namespace \"%s\"", + option->namespace))); + + 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->namespace) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter \"%s.%s\"", + option->namespace, 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", 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", + 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 = optionsValuesToBytea(new_values, spec_set); + + spec_set->postprocess_fun(data, true); + 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 24211807ca..b01462d0cd 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-2022, 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" @@ -36,6 +33,7 @@ #include "utils/guc.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "storage/bufmgr.h" /* * Contents of pg_class.reloptions @@ -93,389 +91,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_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}, @@ -489,17 +106,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}, @@ -507,225 +115,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_for_toast); +void oid_postvalidate(option_value *value); /* * init_local_reloptions @@ -735,9 +127,8 @@ add_reloption(relopt_gen *newoption) void init_local_reloptions(local_relopts *opts, Size relopt_struct_size) { - opts->options = NIL; opts->validators = NIL; - opts->relopt_struct_size = relopt_struct_size; + opts->spec_set = allocateOptionsSpecSet(NULL, relopt_struct_size, true, 0); } /* @@ -751,112 +142,6 @@ register_reloptions_validator(local_relopts *opts, relopts_validator validator) opts->validators = lappend(opts->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 @@ -867,504 +152,71 @@ 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); -} - -/* - * add_local_int_reloption - * Add a new local integer reloption - * - * 'offset' is offset of int-typed field. - */ -void -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); -} - -/* - * add_local_real_reloption - * Add a new local float reloption - * - * '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; + optionsSpecSetAddBool(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val); } - -/* - * 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; - } - - return newoption; -} - -/* - * add_string_reloption - * Add a new string 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. - */ -void -add_string_reloption(bits32 kinds, const char *name, const char *desc, - const char *default_val, validate_string_relopt validator, - LOCKMODE lockmode) -{ - relopt_string *newoption = init_string_reloption(kinds, name, desc, - default_val, - validator, NULL, - lockmode); - - add_reloption((relopt_gen *) newoption); -} - -/* - * add_local_string_reloption - * Add a new local string reloption - * - * 'offset' is offset of int-typed field that will store offset of string value - * in the resulting bytea structure. - */ -void -add_local_string_reloption(local_relopts *relopts, const char *name, - const char *desc, const char *default_val, - 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, - char *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(array, TEXTOID, -1, false, TYPALIGN_INT, - &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(). + * add_local_int_reloption + * Add a new local integer reloption + * + * 'offset' is offset of int-typed field. */ -List * -untransformRelOptions(Datum options) +void +add_local_int_reloption(local_relopts *relopts, const char *name, + const char *desc, int default_val, int min_val, + int max_val, int offset) { - 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); + optionsSpecSetAddInt(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); +} - deconstruct_array(array, TEXTOID, -1, false, TYPALIGN_INT, - &optiondatums, NULL, &noptions); +/* + * add_local_real_reloption + * Add a new local float reloption + * + * '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) +{ + optionsSpecSetAddReal(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); - 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)); - } +/* + * 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, opt_enum_elt_def *members, + int default_val, const char *detailmsg, int offset) +{ + optionsSpecSetAddEnum(relopts->spec_set, name, desc, NoLock, offset, NULL, + members, default_val, detailmsg); +} - return result; +/* + * add_local_string_reloption + * Add a new local string reloption + * + * 'offset' is offset of int-typed field that will store offset of string value + * in the resulting bytea structure. + */ +void +add_local_string_reloption(local_relopts *relopts, const char *name, + const char *desc, const char *default_val, + validate_string_relopt validator, + fill_string_relopt filler, int offset) +{ + optionsSpecSetAddString(relopts->spec_set, name, desc, NoLock, offset, + NULL, default_val, validator, filler); } /* @@ -1381,12 +233,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, @@ -1400,545 +253,308 @@ 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(array, TEXTOID, -1, false, TYPALIGN_INT, - &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. - * - * options is a reloption text array as constructed by transformRelOptions. - * kind specifies the family of options to be processed. - * - * 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. - * - * 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). - * - * 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. + * 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 relopt_value * -parseRelOptions(Datum options, bool validate, relopt_kind kind, - int *numrelopts) +static void +toast_options_postprocess(void *data, bool validate) { - 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++; + StdRdOptions *toast_options = (StdRdOptions *) data; - 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++; - } - } - } - - /* Done if no options */ - if (PointerIsValid(DatumGetPointer(options))) - parseRelOptionsInternal(options, validate, reloptions, numoptions); - - *numrelopts = numoptions; - return reloptions; + toast_options->fillfactor = 100; + toast_options->autovacuum.analyze_threshold = -1; + toast_options->autovacuum.analyze_scale_factor = -1; } -/* 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; - - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); - - values[i].gen = opt->option; - values[i].isset = false; - - i++; - } - - if (options != (Datum) 0) - parseRelOptionsInternal(options, validate, values, nopts); - - return values; -} /* - * Subroutine for parseRelOptions, to parse and validate a single option's - * value + * 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. + * + * Autovacuum related parameters can be set at ShareUpdateExclusiveLock + * since they are only used by the AV procs and don't change anything + * currently executing. + * + * Fillfactor can be set because it applies only to subsequent changes made to + * data blocks, as documented in heapio.c + * + * 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. + * + * 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. */ -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; +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) + stdrd_relopt_spec_set->postprocess_fun = toast_options_postprocess; + + 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); + + 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); } - if (parsed) - option->isset = true; - if (!nofree) - pfree(value); -} - -/* - * 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). - */ -static void * -allocateReloptStruct(Size base, relopt_value *options, int numoptions) -{ - Size size = base; - int i; - - for (i = 0; i < numoptions; i++) - { - relopt_value *optval = &options[i]; - - if (optval->gen->type == RELOPT_TYPE_STRING) - { - relopt_string *optstr = (relopt_string *) optval->gen; + 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\".")); - if (optstr->fill_cb) - { - const char *val = optval->isset ? optval->values.string_val : - optstr->default_isnull ? NULL : optstr->default_val; + 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); - size += optstr->fill_cb(val, NULL); - } - else - size += GET_STRING_RELOPTION_LEN(*optval) + 1; - } - } + 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 palloc0(size); + return stdrd_relopt_spec_set; } -/* - * 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) -{ - int i; - int offset = basesize; - for (i = 0; i < numoptions; i++) - { - int j; - bool found = false; +static options_spec_set *heap_relopt_spec_set = NULL; - 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); +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; } +static options_spec_set *toast_relopt_spec_set = NULL; -/* - * Option parser for anything that uses StdRdOptions. - */ -bytea * -default_reloptions(Datum reloptions, bool validate, relopt_kind kind) +options_spec_set * +get_toast_relopt_spec_set(void) { - 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_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 (toast_relopt_spec_set) + return toast_relopt_spec_set; + toast_relopt_spec_set = get_stdrd_relopt_spec_set(false); + return toast_relopt_spec_set; } -/* - * 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); +static options_spec_set *partitioned_relopt_spec_set = NULL; - /* if none set, we're done */ - if (numoptions == 0) - { - Assert(options == NULL); - return 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); - - pfree(options); +options_spec_set * +get_partitioned_relopt_spec_set(void) +{ + if (partitioned_relopt_spec_set) + return partitioned_relopt_spec_set; + partitioned_relopt_spec_set = allocateOptionsSpecSet( + NULL, sizeof(StdRdOptions), false, 0); + /* No options for now, so spec set is empty */ - return rdopts; + return partitioned_relopt_spec_set; } /* @@ -1949,158 +565,167 @@ 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; - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); + 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 + */ - elems[i].optname = opt->option->name; - elems[i].opttype = opt->option->type; - elems[i].offset = opt->offset; + if (validate && relopts->validators) + { + int val_count = 0; + int i; + option_value *val_array; - i++; - } + foreach(lc, values) + val_count++; + val_array = palloc(sizeof(option_value) * val_count); - vals = parseLocalRelOptions(relopts, options, validate); - opts = allocateReloptStruct(relopts->relopt_struct_size, vals, noptions); - fillRelOptions(opts, relopts->relopt_struct_size, vals, noptions, validate, - elems, noptions); + i = 0; + foreach(lc, values) + { + option_value *val = lfirst(lc); - foreach(lc, relopts->validators) - ((relopts_validator) lfirst(lc)) (opts, vals, noptions); + memcpy(&(val_array[i]), val, sizeof(option_value)); + i++; + } - if (elems) - pfree(elems); + foreach(lc, relopts->validators) + ((relopts_validator) lfirst(lc)) (opts, val_array, val_count); + pfree(val_array); + } return opts; -} -/* - * Option parser for partitioned tables - */ -bytea * -partitioned_table_reloptions(Datum reloptions, bool validate) -{ - /* - * There are no options for partitioned tables yet, but this is able to do - * some validation. - */ - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_PARTITIONED, - 0, NULL, 0); } /* - * 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; } /* @@ -2110,33 +735,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 3d15701a01..a6117665d1 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" @@ -28,6 +28,7 @@ #include "utils/builtins.h" #include "utils/index_selfuncs.h" #include "utils/typcache.h" +#include "utils/guc.h" /* @@ -68,7 +69,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; - amroutine->amoptions = ginoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = ginvalidate; @@ -83,6 +83,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gingetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -605,21 +606,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 * @@ -706,3 +692,29 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build) END_CRIT_SECTION(); } + +static options_spec_set *gin_relopt_specset = NULL; + +void * +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 8c6c744ab7..f7300f5c23 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -90,7 +90,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -105,6 +104,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index d4bf0c7563..435c899110 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 "catalog/pg_opclass.h" #include "common/pg_prng.h" #include "storage/indexfsm.h" @@ -917,20 +917,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. * @@ -1065,3 +1051,39 @@ gistGetFakeLSN(Relation rel) return GetFakeLSNForUnloggedRel(); } } + +/* 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; + +void * +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 fd1a7119b6..31a7117bf2 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -87,7 +87,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -102,6 +101,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index fe37bc47cb..b41d36960b 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 "storage/buf_internals.h" @@ -272,19 +272,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 */ @@ -620,3 +607,22 @@ _hash_kill_items(IndexScanDesc scan) else _hash_relbuf(rel, buf); } + +static options_spec_set *hash_relopt_specset = NULL; + +void * +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 06131f23d4..67cdefa712 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -23,6 +23,7 @@ #include "access/relscan.h" #include "access/xlog.h" #include "access/xloginsert.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -126,7 +127,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -141,6 +141,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = btestimateparallelscan; amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1420,3 +1421,35 @@ btcanreturn(Relation index, int attno) { return true; } + +static options_spec_set *bt_relopt_specset = NULL; + +void * +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 ff260c393a..a08004c045 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" #include "access/relscan.h" #include "catalog/catalog.h" #include "commands/progress.h" @@ -2107,23 +2107,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 a171ca8a08..8d566f08a4 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" @@ -74,7 +74,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -89,6 +88,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -733,22 +733,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). @@ -1348,3 +1332,24 @@ spgproperty(Oid index_oid, int attno, return true; } + +static options_spec_set *spgist_relopt_specset = NULL; + +void * +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/access/transam/xact.c b/src/backend/access/transam/xact.c index 47d80b0d25..bd60b55574 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -122,7 +122,7 @@ bool bsysscan = false; * lookups as fast as possible. */ static FullTransactionId XactTopFullTransactionId = {InvalidTransactionId}; -static int nParallelCurrentXids = 0; +static int nParallelCurrentXids = 0; static TransactionId *ParallelCurrentXids; /* diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 9abbb6b555..c91f38819d 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -90,6 +90,7 @@ create_ctas_internal(List *attrList, IntoClause *into) Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; ObjectAddress intoRelationAddr; + List *toastDefList; /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview = (into->viewQuery != NULL); @@ -124,14 +125,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 ea27857bb8..13e50cd08e 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 eac13ac0b7..c5bd18fa68 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -19,6 +19,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" @@ -531,7 +532,7 @@ DefineIndex(Oid relationId, Form_pg_am accessMethodForm; IndexAmRoutine *amRoutine; bool amcanorder; - amoptions_function amoptions; + amreloptspecset_function amreloptspecsetfn; bool partitioned; bool safe_index; Datum reloptions; @@ -847,7 +848,7 @@ DefineIndex(Oid relationId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; pfree(amRoutine); ReleaseSysCache(tuple); @@ -861,10 +862,18 @@ DefineIndex(Oid relationId, /* * 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. @@ -2039,8 +2048,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, palloc0(sizeof(Datum) * indexInfo->ii_NumIndexAttrs); indexInfo->ii_OpclassOptions[attn] = - transformRelOptions((Datum) 0, attribute->opclassopts, - NULL, NULL, false, false); + optionsDefListToTextArray(attribute->opclassopts); } attn++; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 2de0ebacec..9134af219f 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -20,6 +20,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" @@ -672,7 +673,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -820,19 +820,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 */ + char *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 */ + char *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) @@ -4102,7 +4119,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); @@ -4144,7 +4161,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. @@ -4365,9 +4382,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; @@ -8155,12 +8177,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)); @@ -14141,12 +14164,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]; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + char *heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -14166,38 +14190,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), @@ -14207,11 +14249,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; @@ -14286,20 +14332,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 00ca397fe8..608e3c181b 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -364,10 +364,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 @@ -1100,11 +1099,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 cf222fc3e9..2f98df4d35 100644 --- a/src/backend/foreign/foreign.c +++ b/src/backend/foreign/foreign.c @@ -79,7 +79,7 @@ GetForeignDataWrapperExtended(Oid fdwid, bits16 flags) if (isnull) fdw->options = NIL; else - fdw->options = untransformRelOptions(datum); + fdw->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -166,7 +166,7 @@ GetForeignServerExtended(Oid serverid, bits16 flags) if (isnull) server->options = NIL; else - server->options = untransformRelOptions(datum); + server->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -234,7 +234,7 @@ GetUserMapping(Oid userid, Oid serverid) if (isnull) um->options = NIL; else - um->options = untransformRelOptions(datum); + um->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -271,7 +271,7 @@ GetForeignTable(Oid relid) if (isnull) ft->options = NIL; else - ft->options = untransformRelOptions(datum); + ft->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -304,7 +304,7 @@ GetForeignColumnOptions(Oid relid, AttrNumber attnum) if (isnull) options = NIL; else - options = untransformRelOptions(datum); + options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -512,7 +512,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -609,7 +609,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 1a64a52279..cd44d45f66 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1759,7 +1759,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; @@ -1823,7 +1823,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 0e7b7b3138..e78ca3eb35 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1163,6 +1163,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1186,15 +1187,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); @@ -1303,9 +1305,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 9e252a0891..cc3b910379 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" @@ -148,7 +149,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 60e72f9e8b..3a2411e131 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -461,7 +461,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -476,11 +476,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; @@ -491,7 +491,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 5609246c2f..affe4cc2b4 100644 --- a/src/backend/utils/cache/spccache.c +++ b/src/backend/utils/cache/spccache.c @@ -148,7 +148,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/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index 299b9b7621..9271b76988 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -758,7 +758,7 @@ AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); - if (option_value != NULL) + if (option_value !=NULL) { size_t length = strlen(option_value); char *escaped_value = palloc(1 + 2 * length); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 0b89f399f0..e4ed420179 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -136,10 +136,6 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, double *indexCorrelation, double *indexPages); -/* parse index reloptions */ -typedef bytea *(*amoptions_function) (Datum reloptions, - bool validate); - /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, @@ -186,6 +182,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan); /* restore marked scan position */ typedef void (*amrestrpos_function) (IndexScanDesc scan); +/* get catalog of reloptions definitions */ +typedef void *(*amreloptspecset_function) (); + /* * Callback function signatures - for parallel index scans. */ @@ -265,7 +264,6 @@ typedef struct IndexAmRoutine amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; - amoptions_function amoptions; amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; @@ -277,6 +275,7 @@ typedef struct IndexAmRoutine amendscan_function amendscan; ammarkpos_function ammarkpos; /* can be NULL */ amrestrpos_function amrestrpos; /* can be NULL */ + amreloptspecset_function amreloptspecset; /* can be NULL */ /* interface functions to support parallel index scans */ amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ diff --git a/src/include/access/brin.h b/src/include/access/brin.h index 887fb0a553..0aaf118ea5 100644 --- a/src/include/access/brin.h +++ b/src/include/access/brin.h @@ -36,6 +36,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 2518660927..a7dbb8a5f8 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -14,6 +14,7 @@ #include "access/amapi.h" #include "storage/bufpage.h" #include "utils/typcache.h" +#include "access/options.h" /* @@ -108,6 +109,7 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bytea *brinoptions(Datum reloptions, bool validate); +extern void *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 2935d2f353..e69fd6fd52 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -108,6 +108,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 void *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 240131ef71..7e586061e7 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 */ @@ -478,7 +480,7 @@ extern void gistadjustmembers(Oid opfamilyoid, #define GIST_MIN_FILLFACTOR 10 #define GIST_DEFAULT_FILLFACTOR 90 -extern bytea *gistoptions(Datum reloptions, bool validate); +extern void *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 da372841c4..12ff5ffcbb 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, @@ -472,6 +471,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 void *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 93f8267b48..3979cb4e47 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1254,7 +1254,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 void *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 0000000000..6b7a37d20c --- /dev/null +++ b/src/include/access/options.h @@ -0,0 +1,262 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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; + +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 catalog + * 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 *namespace; /* 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 *namespace; + 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 *namespace, + 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, + char **allowed_namespaces); +extern List *optionsDefListFilterNamespaces(List *defList, + const char *namespace); +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 f740513303..1f4b6d9c28 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-2022, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -22,178 +17,40 @@ #include "access/amapi.h" #include "access/htup.h" #include "access/tupdesc.h" +#include "access/options.h" #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 *opts, Size relopt_struct_size); extern void register_reloptions_validator(local_relopts *opts, @@ -210,7 +67,7 @@ extern void add_local_real_reloption(local_relopts *opts, 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 *opts, const char *name, @@ -219,29 +76,17 @@ extern void add_local_string_reloption(local_relopts *opts, const char *name, validate_string_relopt validator, fill_string_relopt filler, int offset); -extern Datum transformRelOptions(Datum oldOptions, List *defList, - const char *namspace, char *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 5ad4f85940..99664a4114 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 eb56b1c6b8..d3ffe645c3 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -529,6 +529,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 void *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 5d4037f26e..1086723b00 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -34,7 +34,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 22578b6246..17a734e5b1 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,7 +14,7 @@ #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" @@ -25,12 +25,6 @@ PG_MODULE_MAGIC; void _PG_init(void); -/* parse table for fillRelOptions */ -relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -relopt_kind di_relopt_kind; - typedef enum DummyAmEnum { DUMMY_AM_ENUM_ONE, @@ -49,7 +43,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -63,77 +57,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; +void *digetreloptspecset(void); + +void * +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. */ @@ -218,19 +217,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. */ @@ -309,7 +295,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = divacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -323,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/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index b6aef6f654..72fee4176b 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -164,7 +164,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 @@ -183,6 +183,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 4252b0202f..fadce3384d 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -105,6 +105,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 -- diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 4fb746930a..1dbf3cb5c7 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3103,9 +3103,9 @@ amgettuple_function aminitparallelscan_function aminsert_function ammarkpos_function -amoptions_function amparallelrescan_function amproperty_function +amreloptspecset_function amrescan_function amrestrpos_function amvacuumcleanup_function @@ -3259,6 +3259,7 @@ file_type_t filehash_hash filehash_iterator filemap_t +fill_string_option fill_string_relopt finalize_primnode_context find_dependent_phvs_context @@ -3386,7 +3387,6 @@ libpq_source line_t lineno_t list_sort_comparator -local_relopt local_relopts local_source locale_t @@ -3444,6 +3444,18 @@ oidvector on_dsm_detach_callback on_exit_nicely_callback openssl_tls_init_hook_typ +opt_enum_elt_def +option_spec_basic +option_spec_bool +option_spec_enum +option_spec_int +option_spec_real +option_spec_string +option_type +option_value +option_value_postvalidate +option_value_status +options_spec_set ossl_EVP_cipher_func other output_type @@ -3556,6 +3568,7 @@ pointer polymorphic_actuals pos_trgm post_parse_analyze_hook_type +postprocess_bytea_options_function postprocess_result_function pqbool pqsigfunc @@ -3610,16 +3623,6 @@ registered_buffer regmatch_t regoff_t regproc -relopt_bool -relopt_enum -relopt_enum_elt_def -relopt_gen -relopt_int -relopt_kind -relopt_parse_elt -relopt_real -relopt_string -relopt_type relopt_value relopts_validator remoteConn @@ -3766,6 +3769,7 @@ uuid_sortsupport_state uuid_t va_list vacuumingOptions +validate_string_option validate_string_relopt varatt_expanded varattrib_1b -- 2.30.2 --nextPart2827546.QrnxEpBih7-- --nextPart1798714.Ef6jCgUoby Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmKFFO8ACgkQPMbfuIHA GpgvKgf/eZqSXOHW1Qt7N8ht300gSiqrsWUgyH3IhnbduW+p7RA3psp1Eq4aJbIy 8pSzog/rZEHup/9Cew0u3zyLiguHdkVmqSt86DZOvdyKcMz32qyRrkdV1CwJSCC2 EkFCFQrNQ4BZd0jsZE0nImsjz5/qsa1fb9h9vevzDrcsuYNeHuaqoNuY64npbIUC 1Plp39vwMsMzWbvujcYoo3TmSdV614c3anlxWBHXGEI2/fSq/nKjOJyD6uwI1kWy gWhLvtQYmSRCFjapN4MiTnWYLtugMC/FrEdRn7pwaCRTXhckgJrsDmEM4bUD5R1m k49hFU3L9p9vrnQtFOnLDApzW2/Mag== =0sJj -----END PGP SIGNATURE----- --nextPart1798714.Ef6jCgUoby-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH] New options engine @ 2022-05-18 15:25 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2022-05-18 15:25 UTC (permalink / raw) 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 +- src/backend/access/brin/brin.c | 47 +- src/backend/access/common/Makefile | 1 + src/backend/access/common/options.c | 1364 ++++++++++ src/backend/access/common/reloptions.c | 2317 ++++------------- 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 | 19 +- src/backend/access/spgist/spgutils.c | 41 +- src/backend/access/transam/xact.c | 2 +- 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/bin/pg_basebackup/streamutil.c | 2 +- src/include/access/amapi.h | 9 +- src/include/access/brin.h | 2 + src/include/access/brin_internal.h | 2 + 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 | 262 ++ src/include/access/reloptions.h | 205 +- 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 +- src/test/regress/expected/reloptions.out | 13 +- src/test/regress/sql/reloptions.sql | 7 + src/tools/pgindent/typedefs.list | 28 +- 47 files changed, 2621 insertions(+), 2379 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 9966f3f46e..9a009bdbb3 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" @@ -207,7 +208,8 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *bloptions(Datum reloptions, bool validate); +extern void *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 a434cf93ef..522da7dc9f 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 "catalog/index.h" #include "commands/vacuum.h" @@ -34,52 +34,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->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; - amroutine->amoptions = bloptions; + amroutine->amreloptspecset = blrelopt_specset; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = blvalidate; @@ -154,6 +114,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. */ @@ -474,24 +443,37 @@ BloomInitMetapage(Relation index) UnlockReleaseBuffer(metaBuffer); } -/* - * Parse reloptions for bloom index, producing a BloomOptions struct. - */ -bytea * -bloptions(Datum reloptions, bool validate) +void * +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 a561d1d652..2aafe2df83 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -1977,7 +1977,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 4773cadec0..1de15ce09b 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -195,7 +195,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 572591a558..c56457140f 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -73,7 +73,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 e89dc74a5e..af46f0a3ee 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/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 52f171772d..a1eb51bcd6 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" @@ -40,7 +39,6 @@ #include "utils/memutils.h" #include "utils/rel.h" - /* * We use a BrinBuildState during initial construction of a BRIN index. * The running state is kept in a BrinMemTuple. @@ -120,7 +118,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -135,6 +132,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -963,23 +961,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. @@ -1785,3 +1766,29 @@ check_null_keys(BrinValues *bval, ScanKey *nullkeys, int nnullkeys) return true; } + +static options_spec_set *brin_relopt_specset = NULL; + +void * +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 b9aff0ccfd..78c9c5a8d2 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/options.c b/src/backend/access/common/options.c new file mode 100644 index 0000000000..efc377285e --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1364 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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 therm "specification" instead of "definition" because therm + * "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 Vale 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 vales. 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 + offest. + */ + +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 Option 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. + * + * size_of_bytea - 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 *namespace, 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 (namespace) + { + spec_set->namespace = palloc(strlen(namespace) + 1); + strcpy(spec_set->namespace, namespace); + } + else + spec_set->namespace = 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", 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 objec 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->namespace = palloc(strlen(def->defnamespace) + 1); + strcpy(option_dst->namespace, def->defnamespace); + } + else + option_dst->namespace = 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(array, TEXTOID, -1, false, 'i', + &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 i; + 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 (i = 0; i < text_len; i = i + pg_mblen(text_str)) + { + if (text_str[i] == '=') + { + name_len = i; + 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->namespace = 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_namespaces array. Items without namespace are also accepted + * + * Used while validation of syntax parser output. Error is thrown if unproper + * namespace is found. + */ +void +optionsDefListValdateNamespaces(List *defList, char **allowed_namespaces) +{ + 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_namespaces[i]) + { + if (strcmp(def->defnamespace, + allowed_namespaces[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 "namespace" namespace. If "namespace" + * is NULL, only namespaseless options are returned + */ +List * +optionsDefListFilterNamespaces(List *defList, const char *namespace) +{ + ListCell *cell; + List *result = NIL; + + foreach(cell, defList) + { + DefElem *def = (DefElem *) lfirst(cell); + + if ((!namespace && !def->defnamespace) || + (namespace && def->defnamespace && + strcmp(namespace, 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(array, TEXTOID, -1, false, 'i', + &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->namespace && (!spec_set->namespace || + strcmp(spec_set->namespace, option->namespace) != 0)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter namespace \"%s\"", + option->namespace))); + + 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->namespace) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter \"%s.%s\"", + option->namespace, 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", 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", + 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 = optionsValuesToBytea(new_values, spec_set); + + spec_set->postprocess_fun(data, true); + 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 24211807ca..b01462d0cd 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-2022, 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" @@ -36,6 +33,7 @@ #include "utils/guc.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "storage/bufmgr.h" /* * Contents of pg_class.reloptions @@ -93,389 +91,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_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}, @@ -489,17 +106,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}, @@ -507,225 +115,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_for_toast); +void oid_postvalidate(option_value *value); /* * init_local_reloptions @@ -735,9 +127,8 @@ add_reloption(relopt_gen *newoption) void init_local_reloptions(local_relopts *opts, Size relopt_struct_size) { - opts->options = NIL; opts->validators = NIL; - opts->relopt_struct_size = relopt_struct_size; + opts->spec_set = allocateOptionsSpecSet(NULL, relopt_struct_size, true, 0); } /* @@ -751,112 +142,6 @@ register_reloptions_validator(local_relopts *opts, relopts_validator validator) opts->validators = lappend(opts->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 @@ -867,504 +152,71 @@ 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); -} - -/* - * add_local_int_reloption - * Add a new local integer reloption - * - * 'offset' is offset of int-typed field. - */ -void -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); -} - -/* - * add_local_real_reloption - * Add a new local float reloption - * - * '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; + optionsSpecSetAddBool(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val); } - -/* - * 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; - } - - return newoption; -} - -/* - * add_string_reloption - * Add a new string 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. - */ -void -add_string_reloption(bits32 kinds, const char *name, const char *desc, - const char *default_val, validate_string_relopt validator, - LOCKMODE lockmode) -{ - relopt_string *newoption = init_string_reloption(kinds, name, desc, - default_val, - validator, NULL, - lockmode); - - add_reloption((relopt_gen *) newoption); -} - -/* - * add_local_string_reloption - * Add a new local string reloption - * - * 'offset' is offset of int-typed field that will store offset of string value - * in the resulting bytea structure. - */ -void -add_local_string_reloption(local_relopts *relopts, const char *name, - const char *desc, const char *default_val, - 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, - char *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(array, TEXTOID, -1, false, TYPALIGN_INT, - &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(). + * add_local_int_reloption + * Add a new local integer reloption + * + * 'offset' is offset of int-typed field. */ -List * -untransformRelOptions(Datum options) +void +add_local_int_reloption(local_relopts *relopts, const char *name, + const char *desc, int default_val, int min_val, + int max_val, int offset) { - 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); + optionsSpecSetAddInt(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); +} - deconstruct_array(array, TEXTOID, -1, false, TYPALIGN_INT, - &optiondatums, NULL, &noptions); +/* + * add_local_real_reloption + * Add a new local float reloption + * + * '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) +{ + optionsSpecSetAddReal(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); - 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)); - } +/* + * 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, opt_enum_elt_def *members, + int default_val, const char *detailmsg, int offset) +{ + optionsSpecSetAddEnum(relopts->spec_set, name, desc, NoLock, offset, NULL, + members, default_val, detailmsg); +} - return result; +/* + * add_local_string_reloption + * Add a new local string reloption + * + * 'offset' is offset of int-typed field that will store offset of string value + * in the resulting bytea structure. + */ +void +add_local_string_reloption(local_relopts *relopts, const char *name, + const char *desc, const char *default_val, + validate_string_relopt validator, + fill_string_relopt filler, int offset) +{ + optionsSpecSetAddString(relopts->spec_set, name, desc, NoLock, offset, + NULL, default_val, validator, filler); } /* @@ -1381,12 +233,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, @@ -1400,545 +253,308 @@ 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(array, TEXTOID, -1, false, TYPALIGN_INT, - &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. - * - * options is a reloption text array as constructed by transformRelOptions. - * kind specifies the family of options to be processed. - * - * 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. - * - * 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). - * - * 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. + * 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 relopt_value * -parseRelOptions(Datum options, bool validate, relopt_kind kind, - int *numrelopts) +static void +toast_options_postprocess(void *data, bool validate) { - 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++; + StdRdOptions *toast_options = (StdRdOptions *) data; - 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++; - } - } - } - - /* Done if no options */ - if (PointerIsValid(DatumGetPointer(options))) - parseRelOptionsInternal(options, validate, reloptions, numoptions); - - *numrelopts = numoptions; - return reloptions; + toast_options->fillfactor = 100; + toast_options->autovacuum.analyze_threshold = -1; + toast_options->autovacuum.analyze_scale_factor = -1; } -/* 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; - - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); - - values[i].gen = opt->option; - values[i].isset = false; - - i++; - } - - if (options != (Datum) 0) - parseRelOptionsInternal(options, validate, values, nopts); - - return values; -} /* - * Subroutine for parseRelOptions, to parse and validate a single option's - * value + * 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. + * + * Autovacuum related parameters can be set at ShareUpdateExclusiveLock + * since they are only used by the AV procs and don't change anything + * currently executing. + * + * Fillfactor can be set because it applies only to subsequent changes made to + * data blocks, as documented in heapio.c + * + * 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. + * + * 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. */ -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; +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) + stdrd_relopt_spec_set->postprocess_fun = toast_options_postprocess; + + 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); + + 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); } - if (parsed) - option->isset = true; - if (!nofree) - pfree(value); -} - -/* - * 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). - */ -static void * -allocateReloptStruct(Size base, relopt_value *options, int numoptions) -{ - Size size = base; - int i; - - for (i = 0; i < numoptions; i++) - { - relopt_value *optval = &options[i]; - - if (optval->gen->type == RELOPT_TYPE_STRING) - { - relopt_string *optstr = (relopt_string *) optval->gen; + 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\".")); - if (optstr->fill_cb) - { - const char *val = optval->isset ? optval->values.string_val : - optstr->default_isnull ? NULL : optstr->default_val; + 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); - size += optstr->fill_cb(val, NULL); - } - else - size += GET_STRING_RELOPTION_LEN(*optval) + 1; - } - } + 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 palloc0(size); + return stdrd_relopt_spec_set; } -/* - * 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) -{ - int i; - int offset = basesize; - for (i = 0; i < numoptions; i++) - { - int j; - bool found = false; +static options_spec_set *heap_relopt_spec_set = NULL; - 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); +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; } +static options_spec_set *toast_relopt_spec_set = NULL; -/* - * Option parser for anything that uses StdRdOptions. - */ -bytea * -default_reloptions(Datum reloptions, bool validate, relopt_kind kind) +options_spec_set * +get_toast_relopt_spec_set(void) { - 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_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 (toast_relopt_spec_set) + return toast_relopt_spec_set; + toast_relopt_spec_set = get_stdrd_relopt_spec_set(false); + return toast_relopt_spec_set; } -/* - * 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); +static options_spec_set *partitioned_relopt_spec_set = NULL; - /* if none set, we're done */ - if (numoptions == 0) - { - Assert(options == NULL); - return 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); - - pfree(options); +options_spec_set * +get_partitioned_relopt_spec_set(void) +{ + if (partitioned_relopt_spec_set) + return partitioned_relopt_spec_set; + partitioned_relopt_spec_set = allocateOptionsSpecSet( + NULL, sizeof(StdRdOptions), false, 0); + /* No options for now, so spec set is empty */ - return rdopts; + return partitioned_relopt_spec_set; } /* @@ -1949,158 +565,167 @@ 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; - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); + 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 + */ - elems[i].optname = opt->option->name; - elems[i].opttype = opt->option->type; - elems[i].offset = opt->offset; + if (validate && relopts->validators) + { + int val_count = 0; + int i; + option_value *val_array; - i++; - } + foreach(lc, values) + val_count++; + val_array = palloc(sizeof(option_value) * val_count); - vals = parseLocalRelOptions(relopts, options, validate); - opts = allocateReloptStruct(relopts->relopt_struct_size, vals, noptions); - fillRelOptions(opts, relopts->relopt_struct_size, vals, noptions, validate, - elems, noptions); + i = 0; + foreach(lc, values) + { + option_value *val = lfirst(lc); - foreach(lc, relopts->validators) - ((relopts_validator) lfirst(lc)) (opts, vals, noptions); + memcpy(&(val_array[i]), val, sizeof(option_value)); + i++; + } - if (elems) - pfree(elems); + foreach(lc, relopts->validators) + ((relopts_validator) lfirst(lc)) (opts, val_array, val_count); + pfree(val_array); + } return opts; -} -/* - * Option parser for partitioned tables - */ -bytea * -partitioned_table_reloptions(Datum reloptions, bool validate) -{ - /* - * There are no options for partitioned tables yet, but this is able to do - * some validation. - */ - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_PARTITIONED, - 0, NULL, 0); } /* - * 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; } /* @@ -2110,33 +735,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 3d15701a01..a6117665d1 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" @@ -28,6 +28,7 @@ #include "utils/builtins.h" #include "utils/index_selfuncs.h" #include "utils/typcache.h" +#include "utils/guc.h" /* @@ -68,7 +69,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; - amroutine->amoptions = ginoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = ginvalidate; @@ -83,6 +83,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gingetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -605,21 +606,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 * @@ -706,3 +692,29 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build) END_CRIT_SECTION(); } + +static options_spec_set *gin_relopt_specset = NULL; + +void * +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 8c6c744ab7..f7300f5c23 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -90,7 +90,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -105,6 +104,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index d4bf0c7563..435c899110 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 "catalog/pg_opclass.h" #include "common/pg_prng.h" #include "storage/indexfsm.h" @@ -917,20 +917,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. * @@ -1065,3 +1051,39 @@ gistGetFakeLSN(Relation rel) return GetFakeLSNForUnloggedRel(); } } + +/* 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; + +void * +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 fd1a7119b6..31a7117bf2 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -87,7 +87,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -102,6 +101,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index fe37bc47cb..b41d36960b 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 "storage/buf_internals.h" @@ -272,19 +272,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 */ @@ -620,3 +607,22 @@ _hash_kill_items(IndexScanDesc scan) else _hash_relbuf(rel, buf); } + +static options_spec_set *hash_relopt_specset = NULL; + +void * +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 06131f23d4..67cdefa712 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -23,6 +23,7 @@ #include "access/relscan.h" #include "access/xlog.h" #include "access/xloginsert.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -126,7 +127,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -141,6 +141,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = btestimateparallelscan; amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1420,3 +1421,35 @@ btcanreturn(Relation index, int attno) { return true; } + +static options_spec_set *bt_relopt_specset = NULL; + +void * +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 ff260c393a..a08004c045 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" #include "access/relscan.h" #include "catalog/catalog.h" #include "commands/progress.h" @@ -2107,23 +2107,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 a171ca8a08..8d566f08a4 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" @@ -74,7 +74,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -89,6 +88,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -733,22 +733,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). @@ -1348,3 +1332,24 @@ spgproperty(Oid index_oid, int attno, return true; } + +static options_spec_set *spgist_relopt_specset = NULL; + +void * +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/access/transam/xact.c b/src/backend/access/transam/xact.c index 47d80b0d25..bd60b55574 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -122,7 +122,7 @@ bool bsysscan = false; * lookups as fast as possible. */ static FullTransactionId XactTopFullTransactionId = {InvalidTransactionId}; -static int nParallelCurrentXids = 0; +static int nParallelCurrentXids = 0; static TransactionId *ParallelCurrentXids; /* diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 9abbb6b555..c91f38819d 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -90,6 +90,7 @@ create_ctas_internal(List *attrList, IntoClause *into) Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; ObjectAddress intoRelationAddr; + List *toastDefList; /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview = (into->viewQuery != NULL); @@ -124,14 +125,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 ea27857bb8..13e50cd08e 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 eac13ac0b7..c5bd18fa68 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -19,6 +19,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" @@ -531,7 +532,7 @@ DefineIndex(Oid relationId, Form_pg_am accessMethodForm; IndexAmRoutine *amRoutine; bool amcanorder; - amoptions_function amoptions; + amreloptspecset_function amreloptspecsetfn; bool partitioned; bool safe_index; Datum reloptions; @@ -847,7 +848,7 @@ DefineIndex(Oid relationId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; pfree(amRoutine); ReleaseSysCache(tuple); @@ -861,10 +862,18 @@ DefineIndex(Oid relationId, /* * 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. @@ -2039,8 +2048,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, palloc0(sizeof(Datum) * indexInfo->ii_NumIndexAttrs); indexInfo->ii_OpclassOptions[attn] = - transformRelOptions((Datum) 0, attribute->opclassopts, - NULL, NULL, false, false); + optionsDefListToTextArray(attribute->opclassopts); } attn++; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 2de0ebacec..9134af219f 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -20,6 +20,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" @@ -672,7 +673,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -820,19 +820,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 */ + char *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 */ + char *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) @@ -4102,7 +4119,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); @@ -4144,7 +4161,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. @@ -4365,9 +4382,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; @@ -8155,12 +8177,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)); @@ -14141,12 +14164,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]; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + char *heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -14166,38 +14190,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), @@ -14207,11 +14249,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; @@ -14286,20 +14332,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 00ca397fe8..608e3c181b 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -364,10 +364,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 @@ -1100,11 +1099,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 cf222fc3e9..2f98df4d35 100644 --- a/src/backend/foreign/foreign.c +++ b/src/backend/foreign/foreign.c @@ -79,7 +79,7 @@ GetForeignDataWrapperExtended(Oid fdwid, bits16 flags) if (isnull) fdw->options = NIL; else - fdw->options = untransformRelOptions(datum); + fdw->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -166,7 +166,7 @@ GetForeignServerExtended(Oid serverid, bits16 flags) if (isnull) server->options = NIL; else - server->options = untransformRelOptions(datum); + server->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -234,7 +234,7 @@ GetUserMapping(Oid userid, Oid serverid) if (isnull) um->options = NIL; else - um->options = untransformRelOptions(datum); + um->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -271,7 +271,7 @@ GetForeignTable(Oid relid) if (isnull) ft->options = NIL; else - ft->options = untransformRelOptions(datum); + ft->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -304,7 +304,7 @@ GetForeignColumnOptions(Oid relid, AttrNumber attnum) if (isnull) options = NIL; else - options = untransformRelOptions(datum); + options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -512,7 +512,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -609,7 +609,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 1a64a52279..cd44d45f66 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1759,7 +1759,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; @@ -1823,7 +1823,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 0e7b7b3138..e78ca3eb35 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1163,6 +1163,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1186,15 +1187,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); @@ -1303,9 +1305,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 9e252a0891..cc3b910379 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" @@ -148,7 +149,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 60e72f9e8b..3a2411e131 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -461,7 +461,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -476,11 +476,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; @@ -491,7 +491,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 5609246c2f..affe4cc2b4 100644 --- a/src/backend/utils/cache/spccache.c +++ b/src/backend/utils/cache/spccache.c @@ -148,7 +148,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/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index 299b9b7621..9271b76988 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -758,7 +758,7 @@ AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); - if (option_value != NULL) + if (option_value !=NULL) { size_t length = strlen(option_value); char *escaped_value = palloc(1 + 2 * length); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 0b89f399f0..e4ed420179 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -136,10 +136,6 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, double *indexCorrelation, double *indexPages); -/* parse index reloptions */ -typedef bytea *(*amoptions_function) (Datum reloptions, - bool validate); - /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, @@ -186,6 +182,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan); /* restore marked scan position */ typedef void (*amrestrpos_function) (IndexScanDesc scan); +/* get catalog of reloptions definitions */ +typedef void *(*amreloptspecset_function) (); + /* * Callback function signatures - for parallel index scans. */ @@ -265,7 +264,6 @@ typedef struct IndexAmRoutine amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; - amoptions_function amoptions; amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; @@ -277,6 +275,7 @@ typedef struct IndexAmRoutine amendscan_function amendscan; ammarkpos_function ammarkpos; /* can be NULL */ amrestrpos_function amrestrpos; /* can be NULL */ + amreloptspecset_function amreloptspecset; /* can be NULL */ /* interface functions to support parallel index scans */ amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ diff --git a/src/include/access/brin.h b/src/include/access/brin.h index 887fb0a553..0aaf118ea5 100644 --- a/src/include/access/brin.h +++ b/src/include/access/brin.h @@ -36,6 +36,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 2518660927..a7dbb8a5f8 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -14,6 +14,7 @@ #include "access/amapi.h" #include "storage/bufpage.h" #include "utils/typcache.h" +#include "access/options.h" /* @@ -108,6 +109,7 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bytea *brinoptions(Datum reloptions, bool validate); +extern void *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 2935d2f353..e69fd6fd52 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -108,6 +108,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 void *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 240131ef71..7e586061e7 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 */ @@ -478,7 +480,7 @@ extern void gistadjustmembers(Oid opfamilyoid, #define GIST_MIN_FILLFACTOR 10 #define GIST_DEFAULT_FILLFACTOR 90 -extern bytea *gistoptions(Datum reloptions, bool validate); +extern void *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 da372841c4..12ff5ffcbb 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, @@ -472,6 +471,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 void *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 93f8267b48..3979cb4e47 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1254,7 +1254,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 void *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 0000000000..6b7a37d20c --- /dev/null +++ b/src/include/access/options.h @@ -0,0 +1,262 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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; + +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 catalog + * 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 *namespace; /* 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 *namespace; + 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 *namespace, + 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, + char **allowed_namespaces); +extern List *optionsDefListFilterNamespaces(List *defList, + const char *namespace); +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 f740513303..1f4b6d9c28 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-2022, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -22,178 +17,40 @@ #include "access/amapi.h" #include "access/htup.h" #include "access/tupdesc.h" +#include "access/options.h" #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 *opts, Size relopt_struct_size); extern void register_reloptions_validator(local_relopts *opts, @@ -210,7 +67,7 @@ extern void add_local_real_reloption(local_relopts *opts, 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 *opts, const char *name, @@ -219,29 +76,17 @@ extern void add_local_string_reloption(local_relopts *opts, const char *name, validate_string_relopt validator, fill_string_relopt filler, int offset); -extern Datum transformRelOptions(Datum oldOptions, List *defList, - const char *namspace, char *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 5ad4f85940..99664a4114 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 eb56b1c6b8..d3ffe645c3 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -529,6 +529,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 void *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 5d4037f26e..1086723b00 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -34,7 +34,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 22578b6246..17a734e5b1 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,7 +14,7 @@ #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" @@ -25,12 +25,6 @@ PG_MODULE_MAGIC; void _PG_init(void); -/* parse table for fillRelOptions */ -relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -relopt_kind di_relopt_kind; - typedef enum DummyAmEnum { DUMMY_AM_ENUM_ONE, @@ -49,7 +43,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -63,77 +57,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; +void *digetreloptspecset(void); + +void * +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. */ @@ -218,19 +217,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. */ @@ -309,7 +295,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = divacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -323,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/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index b6aef6f654..72fee4176b 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -164,7 +164,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 @@ -183,6 +183,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 4252b0202f..fadce3384d 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -105,6 +105,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 -- diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 4fb746930a..1dbf3cb5c7 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3103,9 +3103,9 @@ amgettuple_function aminitparallelscan_function aminsert_function ammarkpos_function -amoptions_function amparallelrescan_function amproperty_function +amreloptspecset_function amrescan_function amrestrpos_function amvacuumcleanup_function @@ -3259,6 +3259,7 @@ file_type_t filehash_hash filehash_iterator filemap_t +fill_string_option fill_string_relopt finalize_primnode_context find_dependent_phvs_context @@ -3386,7 +3387,6 @@ libpq_source line_t lineno_t list_sort_comparator -local_relopt local_relopts local_source locale_t @@ -3444,6 +3444,18 @@ oidvector on_dsm_detach_callback on_exit_nicely_callback openssl_tls_init_hook_typ +opt_enum_elt_def +option_spec_basic +option_spec_bool +option_spec_enum +option_spec_int +option_spec_real +option_spec_string +option_type +option_value +option_value_postvalidate +option_value_status +options_spec_set ossl_EVP_cipher_func other output_type @@ -3556,6 +3568,7 @@ pointer polymorphic_actuals pos_trgm post_parse_analyze_hook_type +postprocess_bytea_options_function postprocess_result_function pqbool pqsigfunc @@ -3610,16 +3623,6 @@ registered_buffer regmatch_t regoff_t regproc -relopt_bool -relopt_enum -relopt_enum_elt_def -relopt_gen -relopt_int -relopt_kind -relopt_parse_elt -relopt_real -relopt_string -relopt_type relopt_value relopts_validator remoteConn @@ -3766,6 +3769,7 @@ uuid_sortsupport_state uuid_t va_list vacuumingOptions +validate_string_option validate_string_relopt varatt_expanded varattrib_1b -- 2.30.2 --nextPart2827546.QrnxEpBih7-- --nextPart1798714.Ef6jCgUoby Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmKFFO8ACgkQPMbfuIHA GpgvKgf/eZqSXOHW1Qt7N8ht300gSiqrsWUgyH3IhnbduW+p7RA3psp1Eq4aJbIy 8pSzog/rZEHup/9Cew0u3zyLiguHdkVmqSt86DZOvdyKcMz32qyRrkdV1CwJSCC2 EkFCFQrNQ4BZd0jsZE0nImsjz5/qsa1fb9h9vevzDrcsuYNeHuaqoNuY64npbIUC 1Plp39vwMsMzWbvujcYoo3TmSdV614c3anlxWBHXGEI2/fSq/nKjOJyD6uwI1kWy gWhLvtQYmSRCFjapN4MiTnWYLtugMC/FrEdRn7pwaCRTXhckgJrsDmEM4bUD5R1m k49hFU3L9p9vrnQtFOnLDApzW2/Mag== =0sJj -----END PGP SIGNATURE----- --nextPart1798714.Ef6jCgUoby-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH 1/1] New options engine @ 2022-11-04 19:27 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2022-11-04 19:27 UTC (permalink / raw) 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 +- src/backend/access/brin/brin.c | 47 +- src/backend/access/common/Makefile | 1 + src/backend/access/common/meson.build | 1 + src/backend/access/common/options.c | 1362 ++++++++++ src/backend/access/common/reloptions.c | 2314 ++++------------- 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 | 19 +- 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/bin/pg_basebackup/streamutil.c | 2 +- src/include/access/amapi.h | 9 +- src/include/access/brin.h | 2 + src/include/access/brin_internal.h | 2 + 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 | 262 ++ src/include/access/reloptions.h | 205 +- 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 +- src/test/regress/expected/reloptions.out | 13 +- src/test/regress/sql/reloptions.sql | 7 + src/tools/pgindent/typedefs.list | 28 +- 47 files changed, 2620 insertions(+), 2374 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 6d58e22813..ce7e5a22d1 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" @@ -206,7 +207,8 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *bloptions(Datum reloptions, bool validate); +extern void *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 a434cf93ef..522da7dc9f 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 "catalog/index.h" #include "commands/vacuum.h" @@ -34,52 +34,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->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; - amroutine->amoptions = bloptions; + amroutine->amreloptspecset = blrelopt_specset; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = blvalidate; @@ -154,6 +114,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. */ @@ -474,24 +443,37 @@ BloomInitMetapage(Relation index) UnlockReleaseBuffer(metaBuffer); } -/* - * Parse reloptions for bloom index, producing a BloomOptions struct. - */ -bytea * -bloptions(Datum reloptions, bool validate) +void * +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 9202c35847..6004287062 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -1976,7 +1976,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 67821cd25b..b3ceb1580a 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -196,7 +196,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 fa80ee2a55..42848b35e1 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -71,7 +71,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 e89dc74a5e..af46f0a3ee 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/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 20b7d65b94..72b2c37e3e 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" @@ -41,7 +40,6 @@ #include "utils/memutils.h" #include "utils/rel.h" - /* * We use a BrinBuildState during initial construction of a BRIN index. * The running state is kept in a BrinMemTuple. @@ -120,7 +118,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -135,6 +132,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -962,23 +960,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. @@ -1790,3 +1771,29 @@ check_null_keys(BrinValues *bval, ScanKey *nullkeys, int nnullkeys) return true; } + +static options_spec_set *brin_relopt_specset = NULL; + +void * +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 b9aff0ccfd..78c9c5a8d2 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 857beaa32d..fe8c1274a4 100644 --- a/src/backend/access/common/meson.build +++ b/src/backend/access/common/meson.build @@ -4,6 +4,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 0000000000..c614644b78 --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1362 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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 therm "specification" instead of "definition" because therm + * "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 Vale 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 vales. 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 + offest. + */ + +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 Option 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. + * + * size_of_bytea - 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 *namespace, 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 (namespace) + { + spec_set->namespace = palloc(strlen(namespace) + 1); + strcpy(spec_set->namespace, namespace); + } + else + spec_set->namespace = 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", 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 objec 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->namespace = palloc(strlen(def->defnamespace) + 1); + strcpy(option_dst->namespace, def->defnamespace); + } + else + option_dst->namespace = 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 i; + 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 (i = 0; i < text_len; i = i + pg_mblen(text_str)) + { + if (text_str[i] == '=') + { + name_len = i; + 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->namespace = 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_namespaces array. Items without namespace are also accepted + * + * Used while validation of syntax parser output. Error is thrown if unproper + * namespace is found. + */ +void +optionsDefListValdateNamespaces(List *defList, char **allowed_namespaces) +{ + 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_namespaces[i]) + { + if (strcmp(def->defnamespace, + allowed_namespaces[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 "namespace" namespace. If "namespace" + * is NULL, only namespaseless options are returned + */ +List * +optionsDefListFilterNamespaces(List *defList, const char *namespace) +{ + ListCell *cell; + List *result = NIL; + + foreach(cell, defList) + { + DefElem *def = (DefElem *) lfirst(cell); + + if ((!namespace && !def->defnamespace) || + (namespace && def->defnamespace && + strcmp(namespace, 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->namespace && (!spec_set->namespace || + strcmp(spec_set->namespace, option->namespace) != 0)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter namespace \"%s\"", + option->namespace))); + + 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->namespace) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter \"%s.%s\"", + option->namespace, 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", 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", + 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 = optionsValuesToBytea(new_values, spec_set); + + spec_set->postprocess_fun(data, true); + 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 6458a9c276..d7200e4b86 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-2022, 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" @@ -36,6 +33,7 @@ #include "utils/guc.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "storage/bufmgr.h" /* * Contents of pg_class.reloptions @@ -93,389 +91,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_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}, @@ -489,17 +106,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}, @@ -507,225 +115,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_for_toast); +void oid_postvalidate(option_value *value); /* * init_local_reloptions @@ -735,9 +127,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); } /* @@ -751,112 +142,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 @@ -867,502 +152,71 @@ 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); -} - -/* - * add_local_int_reloption - * Add a new local integer reloption - * - * 'offset' is offset of int-typed field. - */ -void -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); -} - -/* - * add_local_real_reloption - * Add a new local float reloption - * - * '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; + optionsSpecSetAddBool(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val); } - -/* - * 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; - } - - return newoption; -} - -/* - * add_string_reloption - * Add a new string 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. - */ -void -add_string_reloption(bits32 kinds, const char *name, const char *desc, - const char *default_val, validate_string_relopt validator, - LOCKMODE lockmode) -{ - relopt_string *newoption = init_string_reloption(kinds, name, desc, - default_val, - validator, NULL, - lockmode); - - add_reloption((relopt_gen *) newoption); -} - -/* - * add_local_string_reloption - * Add a new local string reloption - * - * 'offset' is offset of int-typed field that will store offset of string value - * in the resulting bytea structure. - */ -void -add_local_string_reloption(local_relopts *relopts, const char *name, - const char *desc, const char *default_val, - 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, - char *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(). + * add_local_int_reloption + * Add a new local integer reloption + * + * 'offset' is offset of int-typed field. */ -List * -untransformRelOptions(Datum options) +void +add_local_int_reloption(local_relopts *relopts, const char *name, + const char *desc, int default_val, int min_val, + int max_val, int offset) { - 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); + optionsSpecSetAddInt(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); +} - deconstruct_array_builtin(array, TEXTOID, &optiondatums, NULL, &noptions); +/* + * add_local_real_reloption + * Add a new local float reloption + * + * '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) +{ + optionsSpecSetAddReal(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); - 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)); - } +/* + * 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, opt_enum_elt_def *members, + int default_val, const char *detailmsg, int offset) +{ + optionsSpecSetAddEnum(relopts->spec_set, name, desc, NoLock, offset, NULL, + members, default_val, detailmsg); +} - return result; +/* + * add_local_string_reloption + * Add a new local string reloption + * + * 'offset' is offset of int-typed field that will store offset of string value + * in the resulting bytea structure. + */ +void +add_local_string_reloption(local_relopts *relopts, const char *name, + const char *desc, const char *default_val, + validate_string_relopt validator, + fill_string_relopt filler, int offset) +{ + optionsSpecSetAddString(relopts->spec_set, name, desc, NoLock, offset, + NULL, default_val, validator, filler); } /* @@ -1379,12 +233,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, @@ -1398,544 +253,308 @@ 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. - * - * options is a reloption text array as constructed by transformRelOptions. - * kind specifies the family of options to be processed. - * - * 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. - * - * 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). - * - * 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. + * 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 relopt_value * -parseRelOptions(Datum options, bool validate, relopt_kind kind, - int *numrelopts) +static void +toast_options_postprocess(void *data, bool validate) { - 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++; + StdRdOptions *toast_options = (StdRdOptions *) data; - 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++; - } - } - } - - /* Done if no options */ - if (PointerIsValid(DatumGetPointer(options))) - parseRelOptionsInternal(options, validate, reloptions, numoptions); - - *numrelopts = numoptions; - return reloptions; + toast_options->fillfactor = 100; + toast_options->autovacuum.analyze_threshold = -1; + toast_options->autovacuum.analyze_scale_factor = -1; } -/* 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; - - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); - - values[i].gen = opt->option; - values[i].isset = false; - - i++; - } - - if (options != (Datum) 0) - parseRelOptionsInternal(options, validate, values, nopts); - - return values; -} /* - * Subroutine for parseRelOptions, to parse and validate a single option's - * value + * 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. + * + * Autovacuum related parameters can be set at ShareUpdateExclusiveLock + * since they are only used by the AV procs and don't change anything + * currently executing. + * + * Fillfactor can be set because it applies only to subsequent changes made to + * data blocks, as documented in heapio.c + * + * 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. + * + * 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. */ -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; +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) + stdrd_relopt_spec_set->postprocess_fun = toast_options_postprocess; + + 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); + + 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); } - if (parsed) - option->isset = true; - if (!nofree) - pfree(value); -} - -/* - * 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). - */ -static void * -allocateReloptStruct(Size base, relopt_value *options, int numoptions) -{ - Size size = base; - int i; - - for (i = 0; i < numoptions; i++) - { - relopt_value *optval = &options[i]; - - if (optval->gen->type == RELOPT_TYPE_STRING) - { - relopt_string *optstr = (relopt_string *) optval->gen; + 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\".")); - if (optstr->fill_cb) - { - const char *val = optval->isset ? optval->values.string_val : - optstr->default_isnull ? NULL : optstr->default_val; + 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); - size += optstr->fill_cb(val, NULL); - } - else - size += GET_STRING_RELOPTION_LEN(*optval) + 1; - } - } + 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 palloc0(size); + return stdrd_relopt_spec_set; } -/* - * 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) -{ - int i; - int offset = basesize; - for (i = 0; i < numoptions; i++) - { - int j; - bool found = false; +static options_spec_set *heap_relopt_spec_set = NULL; - 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); +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; } +static options_spec_set *toast_relopt_spec_set = NULL; -/* - * Option parser for anything that uses StdRdOptions. - */ -bytea * -default_reloptions(Datum reloptions, bool validate, relopt_kind kind) +options_spec_set * +get_toast_relopt_spec_set(void) { - 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_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 (toast_relopt_spec_set) + return toast_relopt_spec_set; + toast_relopt_spec_set = get_stdrd_relopt_spec_set(false); + return toast_relopt_spec_set; } -/* - * 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); +static options_spec_set *partitioned_relopt_spec_set = NULL; - /* if none set, we're done */ - if (numoptions == 0) - { - Assert(options == NULL); - return 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); - - pfree(options); +options_spec_set * +get_partitioned_relopt_spec_set(void) +{ + if (partitioned_relopt_spec_set) + return partitioned_relopt_spec_set; + partitioned_relopt_spec_set = allocateOptionsSpecSet( + NULL, sizeof(StdRdOptions), false, 0); + /* No options for now, so spec set is empty */ - return rdopts; + return partitioned_relopt_spec_set; } /* @@ -1946,158 +565,167 @@ 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; - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); + 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 + */ - elems[i].optname = opt->option->name; - elems[i].opttype = opt->option->type; - elems[i].offset = opt->offset; + if (validate && relopts->validators) + { + int val_count = 0; + int i; + option_value *val_array; - i++; - } + foreach(lc, values) + val_count++; + val_array = palloc(sizeof(option_value) * val_count); - vals = parseLocalRelOptions(relopts, options, validate); - opts = allocateReloptStruct(relopts->relopt_struct_size, vals, noptions); - fillRelOptions(opts, relopts->relopt_struct_size, vals, noptions, validate, - elems, noptions); + i = 0; + foreach(lc, values) + { + option_value *val = lfirst(lc); - foreach(lc, relopts->validators) - ((relopts_validator) lfirst(lc)) (opts, vals, noptions); + memcpy(&(val_array[i]), val, sizeof(option_value)); + i++; + } - if (elems) - pfree(elems); + foreach(lc, relopts->validators) + ((relopts_validator) lfirst(lc)) (opts, val_array, val_count); + pfree(val_array); + } return opts; -} -/* - * Option parser for partitioned tables - */ -bytea * -partitioned_table_reloptions(Datum reloptions, bool validate) -{ - /* - * There are no options for partitioned tables yet, but this is able to do - * some validation. - */ - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_PARTITIONED, - 0, NULL, 0); } /* - * 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; } /* @@ -2107,33 +735,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 6df7f2eaeb..6852cadcab 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" @@ -28,6 +28,7 @@ #include "utils/builtins.h" #include "utils/index_selfuncs.h" #include "utils/typcache.h" +#include "utils/guc.h" /* @@ -67,7 +68,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; - amroutine->amoptions = ginoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = ginvalidate; @@ -82,6 +82,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gingetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -604,21 +605,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 * @@ -705,3 +691,29 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build) END_CRIT_SECTION(); } + +static options_spec_set *gin_relopt_specset = NULL; + +void * +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 30069f139c..bdb81dc8e9 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -89,7 +89,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -104,6 +103,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 1532462317..624bf965b0 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 "catalog/pg_opclass.h" #include "common/pg_prng.h" #include "storage/indexfsm.h" @@ -917,20 +917,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. * @@ -1065,3 +1051,39 @@ gistGetFakeLSN(Relation rel) return GetFakeLSNForUnloggedRel(); } } + +/* 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; + +void * +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 c361509d68..fdd8ae684d 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -86,7 +86,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -101,6 +100,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index 32822dbb6b..c745c7316a 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 "storage/buf_internals.h" @@ -272,19 +272,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 */ @@ -620,3 +607,22 @@ _hash_kill_items(IndexScanDesc scan) else _hash_relbuf(rel, buf); } + +static options_spec_set *hash_relopt_specset = NULL; + +void * +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 b52eca8f38..f7b0927f0b 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -23,6 +23,7 @@ #include "access/relscan.h" #include "access/xlog.h" #include "access/xloginsert.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -125,7 +126,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -140,6 +140,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = btestimateparallelscan; amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1419,3 +1420,35 @@ btcanreturn(Relation index, int attno) { return true; } + +static options_spec_set *bt_relopt_specset = NULL; + +void * +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 ff260c393a..a08004c045 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" #include "access/relscan.h" #include "catalog/catalog.h" #include "commands/progress.h" @@ -2107,23 +2107,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 2c661fcf96..676cc9d4cd 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" @@ -73,7 +73,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -88,6 +87,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -732,22 +732,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). @@ -1347,3 +1331,24 @@ spgproperty(Oid index_oid, int attno, return true; } + +static options_spec_set *spgist_relopt_specset = NULL; + +void * +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 152c29b551..6e9d0f2460 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -90,6 +90,7 @@ create_ctas_internal(List *attrList, IntoClause *into) Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; ObjectAddress intoRelationAddr; + List *toastDefList; /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview = (into->viewQuery != NULL); @@ -124,14 +125,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 91f4dd30de..da4d5da896 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 659e189549..6071e493d3 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -19,6 +19,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" @@ -547,7 +548,7 @@ DefineIndex(Oid relationId, Form_pg_am accessMethodForm; IndexAmRoutine *amRoutine; bool amcanorder; - amoptions_function amoptions; + amreloptspecset_function amreloptspecsetfn; bool partitioned; bool safe_index; Datum reloptions; @@ -862,7 +863,7 @@ DefineIndex(Oid relationId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; pfree(amRoutine); ReleaseSysCache(tuple); @@ -876,10 +877,18 @@ DefineIndex(Oid relationId, /* * 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. @@ -2116,8 +2125,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, palloc0_array(Datum, indexInfo->ii_NumIndexAttrs); indexInfo->ii_OpclassOptions[attn] = - transformRelOptions((Datum) 0, attribute->opclassopts, - NULL, NULL, false, false); + optionsDefListToTextArray(attribute->opclassopts); } attn++; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 6007e10730..d5e8630086 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -20,6 +20,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" @@ -676,7 +677,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -824,19 +824,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 */ + char *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 */ + char *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) @@ -4120,7 +4137,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); @@ -4162,7 +4179,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. @@ -4383,9 +4400,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; @@ -8194,12 +8216,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)); @@ -14178,12 +14201,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]; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + char *heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -14203,38 +14227,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), @@ -14244,11 +14286,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; @@ -14323,20 +14369,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 45b30ca566..1f76ae9790 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -339,10 +339,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 @@ -1058,11 +1057,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 56fcb8edf1..fabb70e8bd 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); @@ -235,7 +235,7 @@ GetUserMapping(Oid userid, Oid serverid) if (isnull) um->options = NIL; else - um->options = untransformRelOptions(datum); + um->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -272,7 +272,7 @@ GetForeignTable(Oid relid) if (isnull) ft->options = NIL; else - ft->options = untransformRelOptions(datum); + ft->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -305,7 +305,7 @@ GetForeignColumnOptions(Oid relid, AttrNumber attnum) if (isnull) options = NIL; else - options = untransformRelOptions(datum); + options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -513,7 +513,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -610,7 +610,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 8140e79d8f..0bbb12b846 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1767,7 +1767,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; @@ -1831,7 +1831,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 247d0816ad..54a0536307 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1163,6 +1163,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1186,15 +1187,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); @@ -1303,9 +1305,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 9e252a0891..cc3b910379 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" @@ -148,7 +149,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 bd6cd4e47b..71e1ac5765 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -462,7 +462,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -477,11 +477,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; @@ -492,7 +492,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 5609246c2f..affe4cc2b4 100644 --- a/src/backend/utils/cache/spccache.c +++ b/src/backend/utils/cache/spccache.c @@ -148,7 +148,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/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index 4ccb90c197..7643bd34a0 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -755,7 +755,7 @@ AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); - if (option_value != NULL) + if (option_value !=NULL) { size_t length = strlen(option_value); char *escaped_value = palloc(1 + 2 * length); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 1dc674d230..afb619f8c4 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -136,10 +136,6 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, double *indexCorrelation, double *indexPages); -/* parse index reloptions */ -typedef bytea *(*amoptions_function) (Datum reloptions, - bool validate); - /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, @@ -186,6 +182,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan); /* restore marked scan position */ typedef void (*amrestrpos_function) (IndexScanDesc scan); +/* get catalog of reloptions definitions */ +typedef void *(*amreloptspecset_function) (); + /* * Callback function signatures - for parallel index scans. */ @@ -263,7 +262,6 @@ typedef struct IndexAmRoutine amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; - amoptions_function amoptions; amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; @@ -275,6 +273,7 @@ typedef struct IndexAmRoutine amendscan_function amendscan; ammarkpos_function ammarkpos; /* can be NULL */ amrestrpos_function amrestrpos; /* can be NULL */ + amreloptspecset_function amreloptspecset; /* can be NULL */ /* interface functions to support parallel index scans */ amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ diff --git a/src/include/access/brin.h b/src/include/access/brin.h index 887fb0a553..0aaf118ea5 100644 --- a/src/include/access/brin.h +++ b/src/include/access/brin.h @@ -36,6 +36,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 2518660927..a7dbb8a5f8 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -14,6 +14,7 @@ #include "access/amapi.h" #include "storage/bufpage.h" #include "utils/typcache.h" +#include "access/options.h" /* @@ -108,6 +109,7 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bytea *brinoptions(Datum reloptions, bool validate); +extern void *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 145ec7743c..4a5a87d002 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -108,6 +108,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 void *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 093bf23443..5f1a2dbd92 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 */ @@ -478,7 +480,7 @@ extern void gistadjustmembers(Oid opfamilyoid, #define GIST_MIN_FILLFACTOR 10 #define GIST_DEFAULT_FILLFACTOR 90 -extern bytea *gistoptions(Datum reloptions, bool validate); +extern void *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 da372841c4..12ff5ffcbb 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, @@ -472,6 +471,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 void *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 8e4f6864e5..e40566b238 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1253,7 +1253,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 void *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 0000000000..6b7a37d20c --- /dev/null +++ b/src/include/access/options.h @@ -0,0 +1,262 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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; + +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 catalog + * 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 *namespace; /* 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 *namespace; + 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 *namespace, + 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, + char **allowed_namespaces); +extern List *optionsDefListFilterNamespaces(List *defList, + const char *namespace); +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 21bde78ed0..93acc02cf7 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-2022, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -22,178 +17,40 @@ #include "access/amapi.h" #include "access/htup.h" #include "access/tupdesc.h" +#include "access/options.h" #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 +67,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 +76,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, char *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 5ad4f85940..99664a4114 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 eb56b1c6b8..d3ffe645c3 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -529,6 +529,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 void *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 03f14d6be1..493fa0ce5d 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -34,7 +34,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 67c30394c8..d715c7f2c1 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,7 +14,7 @@ #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" @@ -23,11 +23,7 @@ PG_MODULE_MAGIC; -/* parse table for fillRelOptions */ -relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -relopt_kind di_relopt_kind; +void _PG_init(void); typedef enum DummyAmEnum { @@ -47,7 +43,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -61,77 +57,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; +void *digetreloptspecset(void); + +void * +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. */ @@ -216,19 +217,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. */ @@ -306,7 +294,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = divacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -320,12 +307,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/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index b6aef6f654..72fee4176b 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -164,7 +164,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 @@ -183,6 +183,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 4252b0202f..fadce3384d 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -105,6 +105,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 -- diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 9683b0a88e..ed0db93d81 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3099,9 +3099,9 @@ amgettuple_function aminitparallelscan_function aminsert_function ammarkpos_function -amoptions_function amparallelrescan_function amproperty_function +amreloptspecset_function amrescan_function amrestrpos_function amvacuumcleanup_function @@ -3257,6 +3257,7 @@ file_type_t filehash_hash filehash_iterator filemap_t +fill_string_option fill_string_relopt finalize_primnode_context find_dependent_phvs_context @@ -3384,7 +3385,6 @@ libpq_source line_t lineno_t list_sort_comparator -local_relopt local_relopts local_source locale_t @@ -3442,6 +3442,18 @@ oidvector on_dsm_detach_callback on_exit_nicely_callback openssl_tls_init_hook_typ +opt_enum_elt_def +option_spec_basic +option_spec_bool +option_spec_enum +option_spec_int +option_spec_real +option_spec_string +option_type +option_value +option_value_postvalidate +option_value_status +options_spec_set ossl_EVP_cipher_func other output_type @@ -3553,6 +3565,7 @@ pointer polymorphic_actuals pos_trgm post_parse_analyze_hook_type +postprocess_bytea_options_function postprocess_result_function pqbool pqsigfunc @@ -3608,16 +3621,6 @@ registered_buffer regmatch_t regoff_t regproc -relopt_bool -relopt_enum -relopt_enum_elt_def -relopt_gen -relopt_int -relopt_kind -relopt_parse_elt -relopt_real -relopt_string -relopt_type relopt_value relopts_validator remoteConn @@ -3764,6 +3767,7 @@ uuid_sortsupport_state uuid_t va_list vacuumingOptions +validate_string_option validate_string_relopt varatt_expanded varattrib_1b -- 2.30.2 --nextPart13783817.5KSPCsLXC8-- --nextPart1892771.pVIlkafzbo Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmNlaD4ACgkQPMbfuIHA Gphn+ggAl90HN6zyu8JqSIJnAgS6CYtKkNuVWhvja9ztEyIuoAD4jPAGvnq3wb7/ 7VImgGuXPqF+zbGMli9Fzr89rK7pFvlmqdK1VVNVbh6H012bwTl5T4ZZiteexn4w C5T968jH2T3mbUTJr9C9MmFHcy+ph9lhnuqSJXtzK1+aBnckWwHBRxqs3zSllS1U ZVkbaXFzrFgDb1B5ESks2Zk4wNuhrGmaZjL80RzRu78qLfoWyqtO3i2bIqKoGABp CcbYfwITYOv2b10sWm2xbddHsi9DNUA45I7W2fR6chQ3pFAVWbnSvJhHjHkOOudU ylTSxKmqW0G3gvMGpJdtV7sEyxAhdQ== =xjE9 -----END PGP SIGNATURE----- --nextPart1892771.pVIlkafzbo-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH 1/1] New options engine @ 2022-11-04 19:27 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2022-11-04 19:27 UTC (permalink / raw) 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 +- src/backend/access/brin/brin.c | 47 +- src/backend/access/common/Makefile | 1 + src/backend/access/common/meson.build | 1 + src/backend/access/common/options.c | 1362 ++++++++++ src/backend/access/common/reloptions.c | 2314 ++++------------- 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 | 19 +- 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/bin/pg_basebackup/streamutil.c | 2 +- src/include/access/amapi.h | 9 +- src/include/access/brin.h | 2 + src/include/access/brin_internal.h | 2 + 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 | 262 ++ src/include/access/reloptions.h | 205 +- 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 +- src/test/regress/expected/reloptions.out | 13 +- src/test/regress/sql/reloptions.sql | 7 + src/tools/pgindent/typedefs.list | 28 +- 47 files changed, 2620 insertions(+), 2374 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 6d58e22813..ce7e5a22d1 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" @@ -206,7 +207,8 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *bloptions(Datum reloptions, bool validate); +extern void *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 a434cf93ef..522da7dc9f 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 "catalog/index.h" #include "commands/vacuum.h" @@ -34,52 +34,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->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; - amroutine->amoptions = bloptions; + amroutine->amreloptspecset = blrelopt_specset; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = blvalidate; @@ -154,6 +114,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. */ @@ -474,24 +443,37 @@ BloomInitMetapage(Relation index) UnlockReleaseBuffer(metaBuffer); } -/* - * Parse reloptions for bloom index, producing a BloomOptions struct. - */ -bytea * -bloptions(Datum reloptions, bool validate) +void * +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 9202c35847..6004287062 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -1976,7 +1976,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 67821cd25b..b3ceb1580a 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -196,7 +196,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 fa80ee2a55..42848b35e1 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -71,7 +71,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 e89dc74a5e..af46f0a3ee 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/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 20b7d65b94..72b2c37e3e 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" @@ -41,7 +40,6 @@ #include "utils/memutils.h" #include "utils/rel.h" - /* * We use a BrinBuildState during initial construction of a BRIN index. * The running state is kept in a BrinMemTuple. @@ -120,7 +118,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -135,6 +132,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -962,23 +960,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. @@ -1790,3 +1771,29 @@ check_null_keys(BrinValues *bval, ScanKey *nullkeys, int nnullkeys) return true; } + +static options_spec_set *brin_relopt_specset = NULL; + +void * +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 b9aff0ccfd..78c9c5a8d2 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 857beaa32d..fe8c1274a4 100644 --- a/src/backend/access/common/meson.build +++ b/src/backend/access/common/meson.build @@ -4,6 +4,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 0000000000..c614644b78 --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1362 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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 therm "specification" instead of "definition" because therm + * "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 Vale 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 vales. 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 + offest. + */ + +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 Option 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. + * + * size_of_bytea - 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 *namespace, 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 (namespace) + { + spec_set->namespace = palloc(strlen(namespace) + 1); + strcpy(spec_set->namespace, namespace); + } + else + spec_set->namespace = 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", 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 objec 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->namespace = palloc(strlen(def->defnamespace) + 1); + strcpy(option_dst->namespace, def->defnamespace); + } + else + option_dst->namespace = 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 i; + 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 (i = 0; i < text_len; i = i + pg_mblen(text_str)) + { + if (text_str[i] == '=') + { + name_len = i; + 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->namespace = 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_namespaces array. Items without namespace are also accepted + * + * Used while validation of syntax parser output. Error is thrown if unproper + * namespace is found. + */ +void +optionsDefListValdateNamespaces(List *defList, char **allowed_namespaces) +{ + 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_namespaces[i]) + { + if (strcmp(def->defnamespace, + allowed_namespaces[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 "namespace" namespace. If "namespace" + * is NULL, only namespaseless options are returned + */ +List * +optionsDefListFilterNamespaces(List *defList, const char *namespace) +{ + ListCell *cell; + List *result = NIL; + + foreach(cell, defList) + { + DefElem *def = (DefElem *) lfirst(cell); + + if ((!namespace && !def->defnamespace) || + (namespace && def->defnamespace && + strcmp(namespace, 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->namespace && (!spec_set->namespace || + strcmp(spec_set->namespace, option->namespace) != 0)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter namespace \"%s\"", + option->namespace))); + + 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->namespace) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter \"%s.%s\"", + option->namespace, 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", 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", + 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 = optionsValuesToBytea(new_values, spec_set); + + spec_set->postprocess_fun(data, true); + 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 6458a9c276..d7200e4b86 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-2022, 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" @@ -36,6 +33,7 @@ #include "utils/guc.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "storage/bufmgr.h" /* * Contents of pg_class.reloptions @@ -93,389 +91,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_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}, @@ -489,17 +106,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}, @@ -507,225 +115,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_for_toast); +void oid_postvalidate(option_value *value); /* * init_local_reloptions @@ -735,9 +127,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); } /* @@ -751,112 +142,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 @@ -867,502 +152,71 @@ 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); -} - -/* - * add_local_int_reloption - * Add a new local integer reloption - * - * 'offset' is offset of int-typed field. - */ -void -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); -} - -/* - * add_local_real_reloption - * Add a new local float reloption - * - * '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; + optionsSpecSetAddBool(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val); } - -/* - * 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; - } - - return newoption; -} - -/* - * add_string_reloption - * Add a new string 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. - */ -void -add_string_reloption(bits32 kinds, const char *name, const char *desc, - const char *default_val, validate_string_relopt validator, - LOCKMODE lockmode) -{ - relopt_string *newoption = init_string_reloption(kinds, name, desc, - default_val, - validator, NULL, - lockmode); - - add_reloption((relopt_gen *) newoption); -} - -/* - * add_local_string_reloption - * Add a new local string reloption - * - * 'offset' is offset of int-typed field that will store offset of string value - * in the resulting bytea structure. - */ -void -add_local_string_reloption(local_relopts *relopts, const char *name, - const char *desc, const char *default_val, - 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, - char *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(). + * add_local_int_reloption + * Add a new local integer reloption + * + * 'offset' is offset of int-typed field. */ -List * -untransformRelOptions(Datum options) +void +add_local_int_reloption(local_relopts *relopts, const char *name, + const char *desc, int default_val, int min_val, + int max_val, int offset) { - 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); + optionsSpecSetAddInt(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); +} - deconstruct_array_builtin(array, TEXTOID, &optiondatums, NULL, &noptions); +/* + * add_local_real_reloption + * Add a new local float reloption + * + * '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) +{ + optionsSpecSetAddReal(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); - 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)); - } +/* + * 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, opt_enum_elt_def *members, + int default_val, const char *detailmsg, int offset) +{ + optionsSpecSetAddEnum(relopts->spec_set, name, desc, NoLock, offset, NULL, + members, default_val, detailmsg); +} - return result; +/* + * add_local_string_reloption + * Add a new local string reloption + * + * 'offset' is offset of int-typed field that will store offset of string value + * in the resulting bytea structure. + */ +void +add_local_string_reloption(local_relopts *relopts, const char *name, + const char *desc, const char *default_val, + validate_string_relopt validator, + fill_string_relopt filler, int offset) +{ + optionsSpecSetAddString(relopts->spec_set, name, desc, NoLock, offset, + NULL, default_val, validator, filler); } /* @@ -1379,12 +233,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, @@ -1398,544 +253,308 @@ 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. - * - * options is a reloption text array as constructed by transformRelOptions. - * kind specifies the family of options to be processed. - * - * 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. - * - * 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). - * - * 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. + * 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 relopt_value * -parseRelOptions(Datum options, bool validate, relopt_kind kind, - int *numrelopts) +static void +toast_options_postprocess(void *data, bool validate) { - 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++; + StdRdOptions *toast_options = (StdRdOptions *) data; - 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++; - } - } - } - - /* Done if no options */ - if (PointerIsValid(DatumGetPointer(options))) - parseRelOptionsInternal(options, validate, reloptions, numoptions); - - *numrelopts = numoptions; - return reloptions; + toast_options->fillfactor = 100; + toast_options->autovacuum.analyze_threshold = -1; + toast_options->autovacuum.analyze_scale_factor = -1; } -/* 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; - - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); - - values[i].gen = opt->option; - values[i].isset = false; - - i++; - } - - if (options != (Datum) 0) - parseRelOptionsInternal(options, validate, values, nopts); - - return values; -} /* - * Subroutine for parseRelOptions, to parse and validate a single option's - * value + * 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. + * + * Autovacuum related parameters can be set at ShareUpdateExclusiveLock + * since they are only used by the AV procs and don't change anything + * currently executing. + * + * Fillfactor can be set because it applies only to subsequent changes made to + * data blocks, as documented in heapio.c + * + * 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. + * + * 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. */ -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; +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) + stdrd_relopt_spec_set->postprocess_fun = toast_options_postprocess; + + 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); + + 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); } - if (parsed) - option->isset = true; - if (!nofree) - pfree(value); -} - -/* - * 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). - */ -static void * -allocateReloptStruct(Size base, relopt_value *options, int numoptions) -{ - Size size = base; - int i; - - for (i = 0; i < numoptions; i++) - { - relopt_value *optval = &options[i]; - - if (optval->gen->type == RELOPT_TYPE_STRING) - { - relopt_string *optstr = (relopt_string *) optval->gen; + 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\".")); - if (optstr->fill_cb) - { - const char *val = optval->isset ? optval->values.string_val : - optstr->default_isnull ? NULL : optstr->default_val; + 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); - size += optstr->fill_cb(val, NULL); - } - else - size += GET_STRING_RELOPTION_LEN(*optval) + 1; - } - } + 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 palloc0(size); + return stdrd_relopt_spec_set; } -/* - * 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) -{ - int i; - int offset = basesize; - for (i = 0; i < numoptions; i++) - { - int j; - bool found = false; +static options_spec_set *heap_relopt_spec_set = NULL; - 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); +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; } +static options_spec_set *toast_relopt_spec_set = NULL; -/* - * Option parser for anything that uses StdRdOptions. - */ -bytea * -default_reloptions(Datum reloptions, bool validate, relopt_kind kind) +options_spec_set * +get_toast_relopt_spec_set(void) { - 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_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 (toast_relopt_spec_set) + return toast_relopt_spec_set; + toast_relopt_spec_set = get_stdrd_relopt_spec_set(false); + return toast_relopt_spec_set; } -/* - * 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); +static options_spec_set *partitioned_relopt_spec_set = NULL; - /* if none set, we're done */ - if (numoptions == 0) - { - Assert(options == NULL); - return 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); - - pfree(options); +options_spec_set * +get_partitioned_relopt_spec_set(void) +{ + if (partitioned_relopt_spec_set) + return partitioned_relopt_spec_set; + partitioned_relopt_spec_set = allocateOptionsSpecSet( + NULL, sizeof(StdRdOptions), false, 0); + /* No options for now, so spec set is empty */ - return rdopts; + return partitioned_relopt_spec_set; } /* @@ -1946,158 +565,167 @@ 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; - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); + 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 + */ - elems[i].optname = opt->option->name; - elems[i].opttype = opt->option->type; - elems[i].offset = opt->offset; + if (validate && relopts->validators) + { + int val_count = 0; + int i; + option_value *val_array; - i++; - } + foreach(lc, values) + val_count++; + val_array = palloc(sizeof(option_value) * val_count); - vals = parseLocalRelOptions(relopts, options, validate); - opts = allocateReloptStruct(relopts->relopt_struct_size, vals, noptions); - fillRelOptions(opts, relopts->relopt_struct_size, vals, noptions, validate, - elems, noptions); + i = 0; + foreach(lc, values) + { + option_value *val = lfirst(lc); - foreach(lc, relopts->validators) - ((relopts_validator) lfirst(lc)) (opts, vals, noptions); + memcpy(&(val_array[i]), val, sizeof(option_value)); + i++; + } - if (elems) - pfree(elems); + foreach(lc, relopts->validators) + ((relopts_validator) lfirst(lc)) (opts, val_array, val_count); + pfree(val_array); + } return opts; -} -/* - * Option parser for partitioned tables - */ -bytea * -partitioned_table_reloptions(Datum reloptions, bool validate) -{ - /* - * There are no options for partitioned tables yet, but this is able to do - * some validation. - */ - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_PARTITIONED, - 0, NULL, 0); } /* - * 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; } /* @@ -2107,33 +735,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 6df7f2eaeb..6852cadcab 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" @@ -28,6 +28,7 @@ #include "utils/builtins.h" #include "utils/index_selfuncs.h" #include "utils/typcache.h" +#include "utils/guc.h" /* @@ -67,7 +68,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; - amroutine->amoptions = ginoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = ginvalidate; @@ -82,6 +82,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gingetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -604,21 +605,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 * @@ -705,3 +691,29 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build) END_CRIT_SECTION(); } + +static options_spec_set *gin_relopt_specset = NULL; + +void * +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 30069f139c..bdb81dc8e9 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -89,7 +89,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -104,6 +103,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 1532462317..624bf965b0 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 "catalog/pg_opclass.h" #include "common/pg_prng.h" #include "storage/indexfsm.h" @@ -917,20 +917,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. * @@ -1065,3 +1051,39 @@ gistGetFakeLSN(Relation rel) return GetFakeLSNForUnloggedRel(); } } + +/* 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; + +void * +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 c361509d68..fdd8ae684d 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -86,7 +86,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -101,6 +100,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index 32822dbb6b..c745c7316a 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 "storage/buf_internals.h" @@ -272,19 +272,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 */ @@ -620,3 +607,22 @@ _hash_kill_items(IndexScanDesc scan) else _hash_relbuf(rel, buf); } + +static options_spec_set *hash_relopt_specset = NULL; + +void * +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 b52eca8f38..f7b0927f0b 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -23,6 +23,7 @@ #include "access/relscan.h" #include "access/xlog.h" #include "access/xloginsert.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -125,7 +126,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -140,6 +140,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = btestimateparallelscan; amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1419,3 +1420,35 @@ btcanreturn(Relation index, int attno) { return true; } + +static options_spec_set *bt_relopt_specset = NULL; + +void * +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 ff260c393a..a08004c045 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" #include "access/relscan.h" #include "catalog/catalog.h" #include "commands/progress.h" @@ -2107,23 +2107,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 2c661fcf96..676cc9d4cd 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" @@ -73,7 +73,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -88,6 +87,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -732,22 +732,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). @@ -1347,3 +1331,24 @@ spgproperty(Oid index_oid, int attno, return true; } + +static options_spec_set *spgist_relopt_specset = NULL; + +void * +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 152c29b551..6e9d0f2460 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -90,6 +90,7 @@ create_ctas_internal(List *attrList, IntoClause *into) Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; ObjectAddress intoRelationAddr; + List *toastDefList; /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview = (into->viewQuery != NULL); @@ -124,14 +125,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 91f4dd30de..da4d5da896 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 659e189549..6071e493d3 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -19,6 +19,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" @@ -547,7 +548,7 @@ DefineIndex(Oid relationId, Form_pg_am accessMethodForm; IndexAmRoutine *amRoutine; bool amcanorder; - amoptions_function amoptions; + amreloptspecset_function amreloptspecsetfn; bool partitioned; bool safe_index; Datum reloptions; @@ -862,7 +863,7 @@ DefineIndex(Oid relationId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; pfree(amRoutine); ReleaseSysCache(tuple); @@ -876,10 +877,18 @@ DefineIndex(Oid relationId, /* * 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. @@ -2116,8 +2125,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, palloc0_array(Datum, indexInfo->ii_NumIndexAttrs); indexInfo->ii_OpclassOptions[attn] = - transformRelOptions((Datum) 0, attribute->opclassopts, - NULL, NULL, false, false); + optionsDefListToTextArray(attribute->opclassopts); } attn++; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 6007e10730..d5e8630086 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -20,6 +20,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" @@ -676,7 +677,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -824,19 +824,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 */ + char *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 */ + char *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) @@ -4120,7 +4137,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); @@ -4162,7 +4179,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. @@ -4383,9 +4400,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; @@ -8194,12 +8216,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)); @@ -14178,12 +14201,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]; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + char *heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -14203,38 +14227,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), @@ -14244,11 +14286,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; @@ -14323,20 +14369,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 45b30ca566..1f76ae9790 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -339,10 +339,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 @@ -1058,11 +1057,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 56fcb8edf1..fabb70e8bd 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); @@ -235,7 +235,7 @@ GetUserMapping(Oid userid, Oid serverid) if (isnull) um->options = NIL; else - um->options = untransformRelOptions(datum); + um->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -272,7 +272,7 @@ GetForeignTable(Oid relid) if (isnull) ft->options = NIL; else - ft->options = untransformRelOptions(datum); + ft->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -305,7 +305,7 @@ GetForeignColumnOptions(Oid relid, AttrNumber attnum) if (isnull) options = NIL; else - options = untransformRelOptions(datum); + options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -513,7 +513,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -610,7 +610,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 8140e79d8f..0bbb12b846 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1767,7 +1767,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; @@ -1831,7 +1831,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 247d0816ad..54a0536307 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1163,6 +1163,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1186,15 +1187,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); @@ -1303,9 +1305,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 9e252a0891..cc3b910379 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" @@ -148,7 +149,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 bd6cd4e47b..71e1ac5765 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -462,7 +462,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -477,11 +477,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; @@ -492,7 +492,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 5609246c2f..affe4cc2b4 100644 --- a/src/backend/utils/cache/spccache.c +++ b/src/backend/utils/cache/spccache.c @@ -148,7 +148,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/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index 4ccb90c197..7643bd34a0 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -755,7 +755,7 @@ AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); - if (option_value != NULL) + if (option_value !=NULL) { size_t length = strlen(option_value); char *escaped_value = palloc(1 + 2 * length); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 1dc674d230..afb619f8c4 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -136,10 +136,6 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, double *indexCorrelation, double *indexPages); -/* parse index reloptions */ -typedef bytea *(*amoptions_function) (Datum reloptions, - bool validate); - /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, @@ -186,6 +182,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan); /* restore marked scan position */ typedef void (*amrestrpos_function) (IndexScanDesc scan); +/* get catalog of reloptions definitions */ +typedef void *(*amreloptspecset_function) (); + /* * Callback function signatures - for parallel index scans. */ @@ -263,7 +262,6 @@ typedef struct IndexAmRoutine amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; - amoptions_function amoptions; amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; @@ -275,6 +273,7 @@ typedef struct IndexAmRoutine amendscan_function amendscan; ammarkpos_function ammarkpos; /* can be NULL */ amrestrpos_function amrestrpos; /* can be NULL */ + amreloptspecset_function amreloptspecset; /* can be NULL */ /* interface functions to support parallel index scans */ amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ diff --git a/src/include/access/brin.h b/src/include/access/brin.h index 887fb0a553..0aaf118ea5 100644 --- a/src/include/access/brin.h +++ b/src/include/access/brin.h @@ -36,6 +36,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 2518660927..a7dbb8a5f8 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -14,6 +14,7 @@ #include "access/amapi.h" #include "storage/bufpage.h" #include "utils/typcache.h" +#include "access/options.h" /* @@ -108,6 +109,7 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bytea *brinoptions(Datum reloptions, bool validate); +extern void *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 145ec7743c..4a5a87d002 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -108,6 +108,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 void *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 093bf23443..5f1a2dbd92 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 */ @@ -478,7 +480,7 @@ extern void gistadjustmembers(Oid opfamilyoid, #define GIST_MIN_FILLFACTOR 10 #define GIST_DEFAULT_FILLFACTOR 90 -extern bytea *gistoptions(Datum reloptions, bool validate); +extern void *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 da372841c4..12ff5ffcbb 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, @@ -472,6 +471,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 void *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 8e4f6864e5..e40566b238 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1253,7 +1253,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 void *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 0000000000..6b7a37d20c --- /dev/null +++ b/src/include/access/options.h @@ -0,0 +1,262 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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; + +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 catalog + * 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 *namespace; /* 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 *namespace; + 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 *namespace, + 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, + char **allowed_namespaces); +extern List *optionsDefListFilterNamespaces(List *defList, + const char *namespace); +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 21bde78ed0..93acc02cf7 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-2022, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -22,178 +17,40 @@ #include "access/amapi.h" #include "access/htup.h" #include "access/tupdesc.h" +#include "access/options.h" #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 +67,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 +76,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, char *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 5ad4f85940..99664a4114 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 eb56b1c6b8..d3ffe645c3 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -529,6 +529,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 void *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 03f14d6be1..493fa0ce5d 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -34,7 +34,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 67c30394c8..d715c7f2c1 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,7 +14,7 @@ #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" @@ -23,11 +23,7 @@ PG_MODULE_MAGIC; -/* parse table for fillRelOptions */ -relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -relopt_kind di_relopt_kind; +void _PG_init(void); typedef enum DummyAmEnum { @@ -47,7 +43,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -61,77 +57,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; +void *digetreloptspecset(void); + +void * +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. */ @@ -216,19 +217,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. */ @@ -306,7 +294,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = divacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -320,12 +307,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/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index b6aef6f654..72fee4176b 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -164,7 +164,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 @@ -183,6 +183,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 4252b0202f..fadce3384d 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -105,6 +105,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 -- diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 9683b0a88e..ed0db93d81 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3099,9 +3099,9 @@ amgettuple_function aminitparallelscan_function aminsert_function ammarkpos_function -amoptions_function amparallelrescan_function amproperty_function +amreloptspecset_function amrescan_function amrestrpos_function amvacuumcleanup_function @@ -3257,6 +3257,7 @@ file_type_t filehash_hash filehash_iterator filemap_t +fill_string_option fill_string_relopt finalize_primnode_context find_dependent_phvs_context @@ -3384,7 +3385,6 @@ libpq_source line_t lineno_t list_sort_comparator -local_relopt local_relopts local_source locale_t @@ -3442,6 +3442,18 @@ oidvector on_dsm_detach_callback on_exit_nicely_callback openssl_tls_init_hook_typ +opt_enum_elt_def +option_spec_basic +option_spec_bool +option_spec_enum +option_spec_int +option_spec_real +option_spec_string +option_type +option_value +option_value_postvalidate +option_value_status +options_spec_set ossl_EVP_cipher_func other output_type @@ -3553,6 +3565,7 @@ pointer polymorphic_actuals pos_trgm post_parse_analyze_hook_type +postprocess_bytea_options_function postprocess_result_function pqbool pqsigfunc @@ -3608,16 +3621,6 @@ registered_buffer regmatch_t regoff_t regproc -relopt_bool -relopt_enum -relopt_enum_elt_def -relopt_gen -relopt_int -relopt_kind -relopt_parse_elt -relopt_real -relopt_string -relopt_type relopt_value relopts_validator remoteConn @@ -3764,6 +3767,7 @@ uuid_sortsupport_state uuid_t va_list vacuumingOptions +validate_string_option validate_string_relopt varatt_expanded varattrib_1b -- 2.30.2 --nextPart13783817.5KSPCsLXC8-- --nextPart1892771.pVIlkafzbo Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmNlaD4ACgkQPMbfuIHA Gphn+ggAl90HN6zyu8JqSIJnAgS6CYtKkNuVWhvja9ztEyIuoAD4jPAGvnq3wb7/ 7VImgGuXPqF+zbGMli9Fzr89rK7pFvlmqdK1VVNVbh6H012bwTl5T4ZZiteexn4w C5T968jH2T3mbUTJr9C9MmFHcy+ph9lhnuqSJXtzK1+aBnckWwHBRxqs3zSllS1U ZVkbaXFzrFgDb1B5ESks2Zk4wNuhrGmaZjL80RzRu78qLfoWyqtO3i2bIqKoGABp CcbYfwITYOv2b10sWm2xbddHsi9DNUA45I7W2fR6chQ3pFAVWbnSvJhHjHkOOudU ylTSxKmqW0G3gvMGpJdtV7sEyxAhdQ== =xjE9 -----END PGP SIGNATURE----- --nextPart1892771.pVIlkafzbo-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH 03e] New options engine @ 2022-11-06 16:19 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2022-11-06 16:19 UTC (permalink / raw) 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 +- src/backend/access/brin/brin.c | 47 +- src/backend/access/common/Makefile | 1 + src/backend/access/common/meson.build | 1 + src/backend/access/common/options.c | 1362 ++++++++++ src/backend/access/common/reloptions.c | 2314 ++++------------- 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 | 19 +- 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/bin/pg_basebackup/streamutil.c | 2 +- src/include/access/amapi.h | 9 +- src/include/access/brin.h | 2 + src/include/access/brin_internal.h | 2 + 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 | 262 ++ src/include/access/reloptions.h | 205 +- 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 +- src/test/regress/expected/reloptions.out | 13 +- src/test/regress/sql/reloptions.sql | 7 + src/tools/pgindent/typedefs.list | 28 +- 47 files changed, 2620 insertions(+), 2374 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 6d58e22813..ce7e5a22d1 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" @@ -206,7 +207,8 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *bloptions(Datum reloptions, bool validate); +extern void *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 a434cf93ef..522da7dc9f 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 "catalog/index.h" #include "commands/vacuum.h" @@ -34,52 +34,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->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; - amroutine->amoptions = bloptions; + amroutine->amreloptspecset = blrelopt_specset; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = blvalidate; @@ -154,6 +114,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. */ @@ -474,24 +443,37 @@ BloomInitMetapage(Relation index) UnlockReleaseBuffer(metaBuffer); } -/* - * Parse reloptions for bloom index, producing a BloomOptions struct. - */ -bytea * -bloptions(Datum reloptions, bool validate) +void * +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 9202c35847..6004287062 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -1976,7 +1976,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 67821cd25b..b3ceb1580a 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -196,7 +196,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 fa80ee2a55..42848b35e1 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -71,7 +71,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 e89dc74a5e..af46f0a3ee 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/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 20b7d65b94..72b2c37e3e 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" @@ -41,7 +40,6 @@ #include "utils/memutils.h" #include "utils/rel.h" - /* * We use a BrinBuildState during initial construction of a BRIN index. * The running state is kept in a BrinMemTuple. @@ -120,7 +118,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -135,6 +132,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -962,23 +960,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. @@ -1790,3 +1771,29 @@ check_null_keys(BrinValues *bval, ScanKey *nullkeys, int nnullkeys) return true; } + +static options_spec_set *brin_relopt_specset = NULL; + +void * +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 b9aff0ccfd..78c9c5a8d2 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 857beaa32d..fe8c1274a4 100644 --- a/src/backend/access/common/meson.build +++ b/src/backend/access/common/meson.build @@ -4,6 +4,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 0000000000..c7aab3ab33 --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1362 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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 therm "specification" instead of "definition" because therm + * "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 Vale 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 vales. 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 + offest. + */ + +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 Option 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. + * + * size_of_bytea - 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", 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 objec 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, char **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", 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", + 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 = optionsValuesToBytea(new_values, spec_set); + + spec_set->postprocess_fun(data, true); + 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 6458a9c276..d7200e4b86 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-2022, 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" @@ -36,6 +33,7 @@ #include "utils/guc.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "storage/bufmgr.h" /* * Contents of pg_class.reloptions @@ -93,389 +91,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_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}, @@ -489,17 +106,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}, @@ -507,225 +115,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_for_toast); +void oid_postvalidate(option_value *value); /* * init_local_reloptions @@ -735,9 +127,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); } /* @@ -751,112 +142,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 @@ -867,502 +152,71 @@ 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); -} - -/* - * add_local_int_reloption - * Add a new local integer reloption - * - * 'offset' is offset of int-typed field. - */ -void -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); -} - -/* - * add_local_real_reloption - * Add a new local float reloption - * - * '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; + optionsSpecSetAddBool(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val); } - -/* - * 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; - } - - return newoption; -} - -/* - * add_string_reloption - * Add a new string 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. - */ -void -add_string_reloption(bits32 kinds, const char *name, const char *desc, - const char *default_val, validate_string_relopt validator, - LOCKMODE lockmode) -{ - relopt_string *newoption = init_string_reloption(kinds, name, desc, - default_val, - validator, NULL, - lockmode); - - add_reloption((relopt_gen *) newoption); -} - -/* - * add_local_string_reloption - * Add a new local string reloption - * - * 'offset' is offset of int-typed field that will store offset of string value - * in the resulting bytea structure. - */ -void -add_local_string_reloption(local_relopts *relopts, const char *name, - const char *desc, const char *default_val, - 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, - char *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(). + * add_local_int_reloption + * Add a new local integer reloption + * + * 'offset' is offset of int-typed field. */ -List * -untransformRelOptions(Datum options) +void +add_local_int_reloption(local_relopts *relopts, const char *name, + const char *desc, int default_val, int min_val, + int max_val, int offset) { - 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); + optionsSpecSetAddInt(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); +} - deconstruct_array_builtin(array, TEXTOID, &optiondatums, NULL, &noptions); +/* + * add_local_real_reloption + * Add a new local float reloption + * + * '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) +{ + optionsSpecSetAddReal(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); - 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)); - } +/* + * 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, opt_enum_elt_def *members, + int default_val, const char *detailmsg, int offset) +{ + optionsSpecSetAddEnum(relopts->spec_set, name, desc, NoLock, offset, NULL, + members, default_val, detailmsg); +} - return result; +/* + * add_local_string_reloption + * Add a new local string reloption + * + * 'offset' is offset of int-typed field that will store offset of string value + * in the resulting bytea structure. + */ +void +add_local_string_reloption(local_relopts *relopts, const char *name, + const char *desc, const char *default_val, + validate_string_relopt validator, + fill_string_relopt filler, int offset) +{ + optionsSpecSetAddString(relopts->spec_set, name, desc, NoLock, offset, + NULL, default_val, validator, filler); } /* @@ -1379,12 +233,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, @@ -1398,544 +253,308 @@ 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. - * - * options is a reloption text array as constructed by transformRelOptions. - * kind specifies the family of options to be processed. - * - * 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. - * - * 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). - * - * 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. + * 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 relopt_value * -parseRelOptions(Datum options, bool validate, relopt_kind kind, - int *numrelopts) +static void +toast_options_postprocess(void *data, bool validate) { - 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++; + StdRdOptions *toast_options = (StdRdOptions *) data; - 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++; - } - } - } - - /* Done if no options */ - if (PointerIsValid(DatumGetPointer(options))) - parseRelOptionsInternal(options, validate, reloptions, numoptions); - - *numrelopts = numoptions; - return reloptions; + toast_options->fillfactor = 100; + toast_options->autovacuum.analyze_threshold = -1; + toast_options->autovacuum.analyze_scale_factor = -1; } -/* 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; - - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); - - values[i].gen = opt->option; - values[i].isset = false; - - i++; - } - - if (options != (Datum) 0) - parseRelOptionsInternal(options, validate, values, nopts); - - return values; -} /* - * Subroutine for parseRelOptions, to parse and validate a single option's - * value + * 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. + * + * Autovacuum related parameters can be set at ShareUpdateExclusiveLock + * since they are only used by the AV procs and don't change anything + * currently executing. + * + * Fillfactor can be set because it applies only to subsequent changes made to + * data blocks, as documented in heapio.c + * + * 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. + * + * 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. */ -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; +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) + stdrd_relopt_spec_set->postprocess_fun = toast_options_postprocess; + + 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); + + 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); } - if (parsed) - option->isset = true; - if (!nofree) - pfree(value); -} - -/* - * 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). - */ -static void * -allocateReloptStruct(Size base, relopt_value *options, int numoptions) -{ - Size size = base; - int i; - - for (i = 0; i < numoptions; i++) - { - relopt_value *optval = &options[i]; - - if (optval->gen->type == RELOPT_TYPE_STRING) - { - relopt_string *optstr = (relopt_string *) optval->gen; + 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\".")); - if (optstr->fill_cb) - { - const char *val = optval->isset ? optval->values.string_val : - optstr->default_isnull ? NULL : optstr->default_val; + 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); - size += optstr->fill_cb(val, NULL); - } - else - size += GET_STRING_RELOPTION_LEN(*optval) + 1; - } - } + 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 palloc0(size); + return stdrd_relopt_spec_set; } -/* - * 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) -{ - int i; - int offset = basesize; - for (i = 0; i < numoptions; i++) - { - int j; - bool found = false; +static options_spec_set *heap_relopt_spec_set = NULL; - 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); +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; } +static options_spec_set *toast_relopt_spec_set = NULL; -/* - * Option parser for anything that uses StdRdOptions. - */ -bytea * -default_reloptions(Datum reloptions, bool validate, relopt_kind kind) +options_spec_set * +get_toast_relopt_spec_set(void) { - 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_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 (toast_relopt_spec_set) + return toast_relopt_spec_set; + toast_relopt_spec_set = get_stdrd_relopt_spec_set(false); + return toast_relopt_spec_set; } -/* - * 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); +static options_spec_set *partitioned_relopt_spec_set = NULL; - /* if none set, we're done */ - if (numoptions == 0) - { - Assert(options == NULL); - return 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); - - pfree(options); +options_spec_set * +get_partitioned_relopt_spec_set(void) +{ + if (partitioned_relopt_spec_set) + return partitioned_relopt_spec_set; + partitioned_relopt_spec_set = allocateOptionsSpecSet( + NULL, sizeof(StdRdOptions), false, 0); + /* No options for now, so spec set is empty */ - return rdopts; + return partitioned_relopt_spec_set; } /* @@ -1946,158 +565,167 @@ 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; - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); + 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 + */ - elems[i].optname = opt->option->name; - elems[i].opttype = opt->option->type; - elems[i].offset = opt->offset; + if (validate && relopts->validators) + { + int val_count = 0; + int i; + option_value *val_array; - i++; - } + foreach(lc, values) + val_count++; + val_array = palloc(sizeof(option_value) * val_count); - vals = parseLocalRelOptions(relopts, options, validate); - opts = allocateReloptStruct(relopts->relopt_struct_size, vals, noptions); - fillRelOptions(opts, relopts->relopt_struct_size, vals, noptions, validate, - elems, noptions); + i = 0; + foreach(lc, values) + { + option_value *val = lfirst(lc); - foreach(lc, relopts->validators) - ((relopts_validator) lfirst(lc)) (opts, vals, noptions); + memcpy(&(val_array[i]), val, sizeof(option_value)); + i++; + } - if (elems) - pfree(elems); + foreach(lc, relopts->validators) + ((relopts_validator) lfirst(lc)) (opts, val_array, val_count); + pfree(val_array); + } return opts; -} -/* - * Option parser for partitioned tables - */ -bytea * -partitioned_table_reloptions(Datum reloptions, bool validate) -{ - /* - * There are no options for partitioned tables yet, but this is able to do - * some validation. - */ - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_PARTITIONED, - 0, NULL, 0); } /* - * 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; } /* @@ -2107,33 +735,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 6df7f2eaeb..6852cadcab 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" @@ -28,6 +28,7 @@ #include "utils/builtins.h" #include "utils/index_selfuncs.h" #include "utils/typcache.h" +#include "utils/guc.h" /* @@ -67,7 +68,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; - amroutine->amoptions = ginoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = ginvalidate; @@ -82,6 +82,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gingetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -604,21 +605,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 * @@ -705,3 +691,29 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build) END_CRIT_SECTION(); } + +static options_spec_set *gin_relopt_specset = NULL; + +void * +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 30069f139c..bdb81dc8e9 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -89,7 +89,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -104,6 +103,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 1532462317..624bf965b0 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 "catalog/pg_opclass.h" #include "common/pg_prng.h" #include "storage/indexfsm.h" @@ -917,20 +917,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. * @@ -1065,3 +1051,39 @@ gistGetFakeLSN(Relation rel) return GetFakeLSNForUnloggedRel(); } } + +/* 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; + +void * +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 c361509d68..fdd8ae684d 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -86,7 +86,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -101,6 +100,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index 32822dbb6b..c745c7316a 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 "storage/buf_internals.h" @@ -272,19 +272,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 */ @@ -620,3 +607,22 @@ _hash_kill_items(IndexScanDesc scan) else _hash_relbuf(rel, buf); } + +static options_spec_set *hash_relopt_specset = NULL; + +void * +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 b52eca8f38..f7b0927f0b 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -23,6 +23,7 @@ #include "access/relscan.h" #include "access/xlog.h" #include "access/xloginsert.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -125,7 +126,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -140,6 +140,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = btestimateparallelscan; amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1419,3 +1420,35 @@ btcanreturn(Relation index, int attno) { return true; } + +static options_spec_set *bt_relopt_specset = NULL; + +void * +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 ff260c393a..a08004c045 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" #include "access/relscan.h" #include "catalog/catalog.h" #include "commands/progress.h" @@ -2107,23 +2107,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 2c661fcf96..676cc9d4cd 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" @@ -73,7 +73,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -88,6 +87,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -732,22 +732,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). @@ -1347,3 +1331,24 @@ spgproperty(Oid index_oid, int attno, return true; } + +static options_spec_set *spgist_relopt_specset = NULL; + +void * +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 152c29b551..6e9d0f2460 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -90,6 +90,7 @@ create_ctas_internal(List *attrList, IntoClause *into) Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; ObjectAddress intoRelationAddr; + List *toastDefList; /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview = (into->viewQuery != NULL); @@ -124,14 +125,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 91f4dd30de..da4d5da896 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 659e189549..6071e493d3 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -19,6 +19,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" @@ -547,7 +548,7 @@ DefineIndex(Oid relationId, Form_pg_am accessMethodForm; IndexAmRoutine *amRoutine; bool amcanorder; - amoptions_function amoptions; + amreloptspecset_function amreloptspecsetfn; bool partitioned; bool safe_index; Datum reloptions; @@ -862,7 +863,7 @@ DefineIndex(Oid relationId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; pfree(amRoutine); ReleaseSysCache(tuple); @@ -876,10 +877,18 @@ DefineIndex(Oid relationId, /* * 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. @@ -2116,8 +2125,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, palloc0_array(Datum, indexInfo->ii_NumIndexAttrs); indexInfo->ii_OpclassOptions[attn] = - transformRelOptions((Datum) 0, attribute->opclassopts, - NULL, NULL, false, false); + optionsDefListToTextArray(attribute->opclassopts); } attn++; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 6007e10730..d5e8630086 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -20,6 +20,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" @@ -676,7 +677,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -824,19 +824,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 */ + char *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 */ + char *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) @@ -4120,7 +4137,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); @@ -4162,7 +4179,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. @@ -4383,9 +4400,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; @@ -8194,12 +8216,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)); @@ -14178,12 +14201,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]; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + char *heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -14203,38 +14227,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), @@ -14244,11 +14286,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; @@ -14323,20 +14369,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 45b30ca566..1f76ae9790 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -339,10 +339,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 @@ -1058,11 +1057,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 56fcb8edf1..fabb70e8bd 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); @@ -235,7 +235,7 @@ GetUserMapping(Oid userid, Oid serverid) if (isnull) um->options = NIL; else - um->options = untransformRelOptions(datum); + um->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -272,7 +272,7 @@ GetForeignTable(Oid relid) if (isnull) ft->options = NIL; else - ft->options = untransformRelOptions(datum); + ft->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -305,7 +305,7 @@ GetForeignColumnOptions(Oid relid, AttrNumber attnum) if (isnull) options = NIL; else - options = untransformRelOptions(datum); + options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -513,7 +513,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -610,7 +610,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 8140e79d8f..0bbb12b846 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1767,7 +1767,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; @@ -1831,7 +1831,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 247d0816ad..54a0536307 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1163,6 +1163,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1186,15 +1187,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); @@ -1303,9 +1305,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 9e252a0891..cc3b910379 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" @@ -148,7 +149,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 bd6cd4e47b..71e1ac5765 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -462,7 +462,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -477,11 +477,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; @@ -492,7 +492,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 5609246c2f..affe4cc2b4 100644 --- a/src/backend/utils/cache/spccache.c +++ b/src/backend/utils/cache/spccache.c @@ -148,7 +148,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/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index 4ccb90c197..7643bd34a0 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -755,7 +755,7 @@ AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); - if (option_value != NULL) + if (option_value !=NULL) { size_t length = strlen(option_value); char *escaped_value = palloc(1 + 2 * length); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 1dc674d230..afb619f8c4 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -136,10 +136,6 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, double *indexCorrelation, double *indexPages); -/* parse index reloptions */ -typedef bytea *(*amoptions_function) (Datum reloptions, - bool validate); - /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, @@ -186,6 +182,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan); /* restore marked scan position */ typedef void (*amrestrpos_function) (IndexScanDesc scan); +/* get catalog of reloptions definitions */ +typedef void *(*amreloptspecset_function) (); + /* * Callback function signatures - for parallel index scans. */ @@ -263,7 +262,6 @@ typedef struct IndexAmRoutine amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; - amoptions_function amoptions; amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; @@ -275,6 +273,7 @@ typedef struct IndexAmRoutine amendscan_function amendscan; ammarkpos_function ammarkpos; /* can be NULL */ amrestrpos_function amrestrpos; /* can be NULL */ + amreloptspecset_function amreloptspecset; /* can be NULL */ /* interface functions to support parallel index scans */ amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ diff --git a/src/include/access/brin.h b/src/include/access/brin.h index 887fb0a553..0aaf118ea5 100644 --- a/src/include/access/brin.h +++ b/src/include/access/brin.h @@ -36,6 +36,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 2518660927..a7dbb8a5f8 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -14,6 +14,7 @@ #include "access/amapi.h" #include "storage/bufpage.h" #include "utils/typcache.h" +#include "access/options.h" /* @@ -108,6 +109,7 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bytea *brinoptions(Datum reloptions, bool validate); +extern void *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 145ec7743c..4a5a87d002 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -108,6 +108,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 void *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 093bf23443..5f1a2dbd92 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 */ @@ -478,7 +480,7 @@ extern void gistadjustmembers(Oid opfamilyoid, #define GIST_MIN_FILLFACTOR 10 #define GIST_DEFAULT_FILLFACTOR 90 -extern bytea *gistoptions(Datum reloptions, bool validate); +extern void *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 da372841c4..12ff5ffcbb 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, @@ -472,6 +471,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 void *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 8e4f6864e5..e40566b238 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1253,7 +1253,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 void *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 0000000000..dc5654d31c --- /dev/null +++ b/src/include/access/options.h @@ -0,0 +1,262 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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; + +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 catalog + * 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, + char **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 21bde78ed0..93acc02cf7 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-2022, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -22,178 +17,40 @@ #include "access/amapi.h" #include "access/htup.h" #include "access/tupdesc.h" +#include "access/options.h" #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 +67,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 +76,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, char *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 5ad4f85940..99664a4114 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 eb56b1c6b8..d3ffe645c3 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -529,6 +529,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 void *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 03f14d6be1..493fa0ce5d 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -34,7 +34,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 67c30394c8..d715c7f2c1 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,7 +14,7 @@ #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" @@ -23,11 +23,7 @@ PG_MODULE_MAGIC; -/* parse table for fillRelOptions */ -relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -relopt_kind di_relopt_kind; +void _PG_init(void); typedef enum DummyAmEnum { @@ -47,7 +43,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -61,77 +57,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; +void *digetreloptspecset(void); + +void * +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. */ @@ -216,19 +217,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. */ @@ -306,7 +294,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = divacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -320,12 +307,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/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index b6aef6f654..72fee4176b 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -164,7 +164,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 @@ -183,6 +183,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 4252b0202f..fadce3384d 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -105,6 +105,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 -- diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 9683b0a88e..ed0db93d81 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3099,9 +3099,9 @@ amgettuple_function aminitparallelscan_function aminsert_function ammarkpos_function -amoptions_function amparallelrescan_function amproperty_function +amreloptspecset_function amrescan_function amrestrpos_function amvacuumcleanup_function @@ -3257,6 +3257,7 @@ file_type_t filehash_hash filehash_iterator filemap_t +fill_string_option fill_string_relopt finalize_primnode_context find_dependent_phvs_context @@ -3384,7 +3385,6 @@ libpq_source line_t lineno_t list_sort_comparator -local_relopt local_relopts local_source locale_t @@ -3442,6 +3442,18 @@ oidvector on_dsm_detach_callback on_exit_nicely_callback openssl_tls_init_hook_typ +opt_enum_elt_def +option_spec_basic +option_spec_bool +option_spec_enum +option_spec_int +option_spec_real +option_spec_string +option_type +option_value +option_value_postvalidate +option_value_status +options_spec_set ossl_EVP_cipher_func other output_type @@ -3553,6 +3565,7 @@ pointer polymorphic_actuals pos_trgm post_parse_analyze_hook_type +postprocess_bytea_options_function postprocess_result_function pqbool pqsigfunc @@ -3608,16 +3621,6 @@ registered_buffer regmatch_t regoff_t regproc -relopt_bool -relopt_enum -relopt_enum_elt_def -relopt_gen -relopt_int -relopt_kind -relopt_parse_elt -relopt_real -relopt_string -relopt_type relopt_value relopts_validator remoteConn @@ -3764,6 +3767,7 @@ uuid_sortsupport_state uuid_t va_list vacuumingOptions +validate_string_option validate_string_relopt varatt_expanded varattrib_1b -- 2.30.2 --nextPart7348732.KNHxNKMfV0-- --nextPart8177062.GDmbcLR26a Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmNn3zEACgkQPMbfuIHA GphJXgf7BAyXY3Jc3CLGLkegKTTDJ7ljhFycEmpCF1VnQDPnjQOEEwBtnOGpzcdg tlOMp11RVp8ZzNZhgnbJ+9MopJrVkovR7KEpXaLBd3JChGyEaNcGMiLLw4g6GoAm HOhn83sE/U6fFYI6JGR/4nL/eSJPbHatKEryws96t93pO3g4aA1iZcOhLcTDt7de 5E5rCfC16M36qnfMu5h7/G6f2X3iBPs5yfrQlgTiXyT+zlvyWIy1Ud2gTWu5jZPC Oq4RYqf6lZku64DPIOJJVq5WkiDtPwLmeyRkx3eG5WILhcibUf7gfW7gtbV9KqVA fa9AV3i8XGLNvb3clAn7EOIGDOokQQ== =37V+ -----END PGP SIGNATURE----- --nextPart8177062.GDmbcLR26a-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH 03e] New options engine @ 2022-11-06 16:19 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2022-11-06 16:19 UTC (permalink / raw) 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 +- src/backend/access/brin/brin.c | 47 +- src/backend/access/common/Makefile | 1 + src/backend/access/common/meson.build | 1 + src/backend/access/common/options.c | 1362 ++++++++++ src/backend/access/common/reloptions.c | 2314 ++++------------- 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 | 19 +- 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/bin/pg_basebackup/streamutil.c | 2 +- src/include/access/amapi.h | 9 +- src/include/access/brin.h | 2 + src/include/access/brin_internal.h | 2 + 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 | 262 ++ src/include/access/reloptions.h | 205 +- 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 +- src/test/regress/expected/reloptions.out | 13 +- src/test/regress/sql/reloptions.sql | 7 + src/tools/pgindent/typedefs.list | 28 +- 47 files changed, 2620 insertions(+), 2374 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 6d58e22813..ce7e5a22d1 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" @@ -206,7 +207,8 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *bloptions(Datum reloptions, bool validate); +extern void *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 a434cf93ef..522da7dc9f 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 "catalog/index.h" #include "commands/vacuum.h" @@ -34,52 +34,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->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; - amroutine->amoptions = bloptions; + amroutine->amreloptspecset = blrelopt_specset; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = blvalidate; @@ -154,6 +114,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. */ @@ -474,24 +443,37 @@ BloomInitMetapage(Relation index) UnlockReleaseBuffer(metaBuffer); } -/* - * Parse reloptions for bloom index, producing a BloomOptions struct. - */ -bytea * -bloptions(Datum reloptions, bool validate) +void * +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 9202c35847..6004287062 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -1976,7 +1976,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 67821cd25b..b3ceb1580a 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -196,7 +196,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 fa80ee2a55..42848b35e1 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -71,7 +71,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 e89dc74a5e..af46f0a3ee 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/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 20b7d65b94..72b2c37e3e 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" @@ -41,7 +40,6 @@ #include "utils/memutils.h" #include "utils/rel.h" - /* * We use a BrinBuildState during initial construction of a BRIN index. * The running state is kept in a BrinMemTuple. @@ -120,7 +118,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -135,6 +132,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -962,23 +960,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. @@ -1790,3 +1771,29 @@ check_null_keys(BrinValues *bval, ScanKey *nullkeys, int nnullkeys) return true; } + +static options_spec_set *brin_relopt_specset = NULL; + +void * +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 b9aff0ccfd..78c9c5a8d2 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 857beaa32d..fe8c1274a4 100644 --- a/src/backend/access/common/meson.build +++ b/src/backend/access/common/meson.build @@ -4,6 +4,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 0000000000..c7aab3ab33 --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1362 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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 therm "specification" instead of "definition" because therm + * "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 Vale 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 vales. 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 + offest. + */ + +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 Option 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. + * + * size_of_bytea - 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", 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 objec 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, char **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", 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", + 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 = optionsValuesToBytea(new_values, spec_set); + + spec_set->postprocess_fun(data, true); + 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 6458a9c276..d7200e4b86 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-2022, 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" @@ -36,6 +33,7 @@ #include "utils/guc.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "storage/bufmgr.h" /* * Contents of pg_class.reloptions @@ -93,389 +91,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_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}, @@ -489,17 +106,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}, @@ -507,225 +115,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_for_toast); +void oid_postvalidate(option_value *value); /* * init_local_reloptions @@ -735,9 +127,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); } /* @@ -751,112 +142,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 @@ -867,502 +152,71 @@ 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); -} - -/* - * add_local_int_reloption - * Add a new local integer reloption - * - * 'offset' is offset of int-typed field. - */ -void -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); -} - -/* - * add_local_real_reloption - * Add a new local float reloption - * - * '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; + optionsSpecSetAddBool(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val); } - -/* - * 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; - } - - return newoption; -} - -/* - * add_string_reloption - * Add a new string 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. - */ -void -add_string_reloption(bits32 kinds, const char *name, const char *desc, - const char *default_val, validate_string_relopt validator, - LOCKMODE lockmode) -{ - relopt_string *newoption = init_string_reloption(kinds, name, desc, - default_val, - validator, NULL, - lockmode); - - add_reloption((relopt_gen *) newoption); -} - -/* - * add_local_string_reloption - * Add a new local string reloption - * - * 'offset' is offset of int-typed field that will store offset of string value - * in the resulting bytea structure. - */ -void -add_local_string_reloption(local_relopts *relopts, const char *name, - const char *desc, const char *default_val, - 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, - char *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(). + * add_local_int_reloption + * Add a new local integer reloption + * + * 'offset' is offset of int-typed field. */ -List * -untransformRelOptions(Datum options) +void +add_local_int_reloption(local_relopts *relopts, const char *name, + const char *desc, int default_val, int min_val, + int max_val, int offset) { - 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); + optionsSpecSetAddInt(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); +} - deconstruct_array_builtin(array, TEXTOID, &optiondatums, NULL, &noptions); +/* + * add_local_real_reloption + * Add a new local float reloption + * + * '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) +{ + optionsSpecSetAddReal(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); - 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)); - } +/* + * 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, opt_enum_elt_def *members, + int default_val, const char *detailmsg, int offset) +{ + optionsSpecSetAddEnum(relopts->spec_set, name, desc, NoLock, offset, NULL, + members, default_val, detailmsg); +} - return result; +/* + * add_local_string_reloption + * Add a new local string reloption + * + * 'offset' is offset of int-typed field that will store offset of string value + * in the resulting bytea structure. + */ +void +add_local_string_reloption(local_relopts *relopts, const char *name, + const char *desc, const char *default_val, + validate_string_relopt validator, + fill_string_relopt filler, int offset) +{ + optionsSpecSetAddString(relopts->spec_set, name, desc, NoLock, offset, + NULL, default_val, validator, filler); } /* @@ -1379,12 +233,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, @@ -1398,544 +253,308 @@ 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. - * - * options is a reloption text array as constructed by transformRelOptions. - * kind specifies the family of options to be processed. - * - * 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. - * - * 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). - * - * 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. + * 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 relopt_value * -parseRelOptions(Datum options, bool validate, relopt_kind kind, - int *numrelopts) +static void +toast_options_postprocess(void *data, bool validate) { - 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++; + StdRdOptions *toast_options = (StdRdOptions *) data; - 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++; - } - } - } - - /* Done if no options */ - if (PointerIsValid(DatumGetPointer(options))) - parseRelOptionsInternal(options, validate, reloptions, numoptions); - - *numrelopts = numoptions; - return reloptions; + toast_options->fillfactor = 100; + toast_options->autovacuum.analyze_threshold = -1; + toast_options->autovacuum.analyze_scale_factor = -1; } -/* 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; - - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); - - values[i].gen = opt->option; - values[i].isset = false; - - i++; - } - - if (options != (Datum) 0) - parseRelOptionsInternal(options, validate, values, nopts); - - return values; -} /* - * Subroutine for parseRelOptions, to parse and validate a single option's - * value + * 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. + * + * Autovacuum related parameters can be set at ShareUpdateExclusiveLock + * since they are only used by the AV procs and don't change anything + * currently executing. + * + * Fillfactor can be set because it applies only to subsequent changes made to + * data blocks, as documented in heapio.c + * + * 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. + * + * 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. */ -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; +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) + stdrd_relopt_spec_set->postprocess_fun = toast_options_postprocess; + + 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); + + 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); } - if (parsed) - option->isset = true; - if (!nofree) - pfree(value); -} - -/* - * 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). - */ -static void * -allocateReloptStruct(Size base, relopt_value *options, int numoptions) -{ - Size size = base; - int i; - - for (i = 0; i < numoptions; i++) - { - relopt_value *optval = &options[i]; - - if (optval->gen->type == RELOPT_TYPE_STRING) - { - relopt_string *optstr = (relopt_string *) optval->gen; + 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\".")); - if (optstr->fill_cb) - { - const char *val = optval->isset ? optval->values.string_val : - optstr->default_isnull ? NULL : optstr->default_val; + 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); - size += optstr->fill_cb(val, NULL); - } - else - size += GET_STRING_RELOPTION_LEN(*optval) + 1; - } - } + 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 palloc0(size); + return stdrd_relopt_spec_set; } -/* - * 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) -{ - int i; - int offset = basesize; - for (i = 0; i < numoptions; i++) - { - int j; - bool found = false; +static options_spec_set *heap_relopt_spec_set = NULL; - 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); +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; } +static options_spec_set *toast_relopt_spec_set = NULL; -/* - * Option parser for anything that uses StdRdOptions. - */ -bytea * -default_reloptions(Datum reloptions, bool validate, relopt_kind kind) +options_spec_set * +get_toast_relopt_spec_set(void) { - 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_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 (toast_relopt_spec_set) + return toast_relopt_spec_set; + toast_relopt_spec_set = get_stdrd_relopt_spec_set(false); + return toast_relopt_spec_set; } -/* - * 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); +static options_spec_set *partitioned_relopt_spec_set = NULL; - /* if none set, we're done */ - if (numoptions == 0) - { - Assert(options == NULL); - return 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); - - pfree(options); +options_spec_set * +get_partitioned_relopt_spec_set(void) +{ + if (partitioned_relopt_spec_set) + return partitioned_relopt_spec_set; + partitioned_relopt_spec_set = allocateOptionsSpecSet( + NULL, sizeof(StdRdOptions), false, 0); + /* No options for now, so spec set is empty */ - return rdopts; + return partitioned_relopt_spec_set; } /* @@ -1946,158 +565,167 @@ 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; - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); + 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 + */ - elems[i].optname = opt->option->name; - elems[i].opttype = opt->option->type; - elems[i].offset = opt->offset; + if (validate && relopts->validators) + { + int val_count = 0; + int i; + option_value *val_array; - i++; - } + foreach(lc, values) + val_count++; + val_array = palloc(sizeof(option_value) * val_count); - vals = parseLocalRelOptions(relopts, options, validate); - opts = allocateReloptStruct(relopts->relopt_struct_size, vals, noptions); - fillRelOptions(opts, relopts->relopt_struct_size, vals, noptions, validate, - elems, noptions); + i = 0; + foreach(lc, values) + { + option_value *val = lfirst(lc); - foreach(lc, relopts->validators) - ((relopts_validator) lfirst(lc)) (opts, vals, noptions); + memcpy(&(val_array[i]), val, sizeof(option_value)); + i++; + } - if (elems) - pfree(elems); + foreach(lc, relopts->validators) + ((relopts_validator) lfirst(lc)) (opts, val_array, val_count); + pfree(val_array); + } return opts; -} -/* - * Option parser for partitioned tables - */ -bytea * -partitioned_table_reloptions(Datum reloptions, bool validate) -{ - /* - * There are no options for partitioned tables yet, but this is able to do - * some validation. - */ - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_PARTITIONED, - 0, NULL, 0); } /* - * 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; } /* @@ -2107,33 +735,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 6df7f2eaeb..6852cadcab 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" @@ -28,6 +28,7 @@ #include "utils/builtins.h" #include "utils/index_selfuncs.h" #include "utils/typcache.h" +#include "utils/guc.h" /* @@ -67,7 +68,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; - amroutine->amoptions = ginoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = ginvalidate; @@ -82,6 +82,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gingetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -604,21 +605,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 * @@ -705,3 +691,29 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build) END_CRIT_SECTION(); } + +static options_spec_set *gin_relopt_specset = NULL; + +void * +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 30069f139c..bdb81dc8e9 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -89,7 +89,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -104,6 +103,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 1532462317..624bf965b0 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 "catalog/pg_opclass.h" #include "common/pg_prng.h" #include "storage/indexfsm.h" @@ -917,20 +917,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. * @@ -1065,3 +1051,39 @@ gistGetFakeLSN(Relation rel) return GetFakeLSNForUnloggedRel(); } } + +/* 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; + +void * +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 c361509d68..fdd8ae684d 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -86,7 +86,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -101,6 +100,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index 32822dbb6b..c745c7316a 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 "storage/buf_internals.h" @@ -272,19 +272,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 */ @@ -620,3 +607,22 @@ _hash_kill_items(IndexScanDesc scan) else _hash_relbuf(rel, buf); } + +static options_spec_set *hash_relopt_specset = NULL; + +void * +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 b52eca8f38..f7b0927f0b 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -23,6 +23,7 @@ #include "access/relscan.h" #include "access/xlog.h" #include "access/xloginsert.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -125,7 +126,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -140,6 +140,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = btestimateparallelscan; amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1419,3 +1420,35 @@ btcanreturn(Relation index, int attno) { return true; } + +static options_spec_set *bt_relopt_specset = NULL; + +void * +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 ff260c393a..a08004c045 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" #include "access/relscan.h" #include "catalog/catalog.h" #include "commands/progress.h" @@ -2107,23 +2107,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 2c661fcf96..676cc9d4cd 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" @@ -73,7 +73,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -88,6 +87,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -732,22 +732,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). @@ -1347,3 +1331,24 @@ spgproperty(Oid index_oid, int attno, return true; } + +static options_spec_set *spgist_relopt_specset = NULL; + +void * +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 152c29b551..6e9d0f2460 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -90,6 +90,7 @@ create_ctas_internal(List *attrList, IntoClause *into) Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; ObjectAddress intoRelationAddr; + List *toastDefList; /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview = (into->viewQuery != NULL); @@ -124,14 +125,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 91f4dd30de..da4d5da896 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 659e189549..6071e493d3 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -19,6 +19,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" @@ -547,7 +548,7 @@ DefineIndex(Oid relationId, Form_pg_am accessMethodForm; IndexAmRoutine *amRoutine; bool amcanorder; - amoptions_function amoptions; + amreloptspecset_function amreloptspecsetfn; bool partitioned; bool safe_index; Datum reloptions; @@ -862,7 +863,7 @@ DefineIndex(Oid relationId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; pfree(amRoutine); ReleaseSysCache(tuple); @@ -876,10 +877,18 @@ DefineIndex(Oid relationId, /* * 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. @@ -2116,8 +2125,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, palloc0_array(Datum, indexInfo->ii_NumIndexAttrs); indexInfo->ii_OpclassOptions[attn] = - transformRelOptions((Datum) 0, attribute->opclassopts, - NULL, NULL, false, false); + optionsDefListToTextArray(attribute->opclassopts); } attn++; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 6007e10730..d5e8630086 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -20,6 +20,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" @@ -676,7 +677,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -824,19 +824,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 */ + char *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 */ + char *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) @@ -4120,7 +4137,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); @@ -4162,7 +4179,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. @@ -4383,9 +4400,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; @@ -8194,12 +8216,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)); @@ -14178,12 +14201,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]; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + char *heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -14203,38 +14227,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), @@ -14244,11 +14286,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; @@ -14323,20 +14369,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 45b30ca566..1f76ae9790 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -339,10 +339,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 @@ -1058,11 +1057,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 56fcb8edf1..fabb70e8bd 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); @@ -235,7 +235,7 @@ GetUserMapping(Oid userid, Oid serverid) if (isnull) um->options = NIL; else - um->options = untransformRelOptions(datum); + um->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -272,7 +272,7 @@ GetForeignTable(Oid relid) if (isnull) ft->options = NIL; else - ft->options = untransformRelOptions(datum); + ft->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -305,7 +305,7 @@ GetForeignColumnOptions(Oid relid, AttrNumber attnum) if (isnull) options = NIL; else - options = untransformRelOptions(datum); + options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -513,7 +513,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -610,7 +610,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 8140e79d8f..0bbb12b846 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1767,7 +1767,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; @@ -1831,7 +1831,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 247d0816ad..54a0536307 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1163,6 +1163,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1186,15 +1187,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); @@ -1303,9 +1305,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 9e252a0891..cc3b910379 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" @@ -148,7 +149,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 bd6cd4e47b..71e1ac5765 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -462,7 +462,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -477,11 +477,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; @@ -492,7 +492,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 5609246c2f..affe4cc2b4 100644 --- a/src/backend/utils/cache/spccache.c +++ b/src/backend/utils/cache/spccache.c @@ -148,7 +148,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/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index 4ccb90c197..7643bd34a0 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -755,7 +755,7 @@ AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); - if (option_value != NULL) + if (option_value !=NULL) { size_t length = strlen(option_value); char *escaped_value = palloc(1 + 2 * length); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 1dc674d230..afb619f8c4 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -136,10 +136,6 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, double *indexCorrelation, double *indexPages); -/* parse index reloptions */ -typedef bytea *(*amoptions_function) (Datum reloptions, - bool validate); - /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, @@ -186,6 +182,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan); /* restore marked scan position */ typedef void (*amrestrpos_function) (IndexScanDesc scan); +/* get catalog of reloptions definitions */ +typedef void *(*amreloptspecset_function) (); + /* * Callback function signatures - for parallel index scans. */ @@ -263,7 +262,6 @@ typedef struct IndexAmRoutine amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; - amoptions_function amoptions; amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; @@ -275,6 +273,7 @@ typedef struct IndexAmRoutine amendscan_function amendscan; ammarkpos_function ammarkpos; /* can be NULL */ amrestrpos_function amrestrpos; /* can be NULL */ + amreloptspecset_function amreloptspecset; /* can be NULL */ /* interface functions to support parallel index scans */ amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ diff --git a/src/include/access/brin.h b/src/include/access/brin.h index 887fb0a553..0aaf118ea5 100644 --- a/src/include/access/brin.h +++ b/src/include/access/brin.h @@ -36,6 +36,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 2518660927..a7dbb8a5f8 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -14,6 +14,7 @@ #include "access/amapi.h" #include "storage/bufpage.h" #include "utils/typcache.h" +#include "access/options.h" /* @@ -108,6 +109,7 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bytea *brinoptions(Datum reloptions, bool validate); +extern void *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 145ec7743c..4a5a87d002 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -108,6 +108,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 void *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 093bf23443..5f1a2dbd92 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 */ @@ -478,7 +480,7 @@ extern void gistadjustmembers(Oid opfamilyoid, #define GIST_MIN_FILLFACTOR 10 #define GIST_DEFAULT_FILLFACTOR 90 -extern bytea *gistoptions(Datum reloptions, bool validate); +extern void *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 da372841c4..12ff5ffcbb 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, @@ -472,6 +471,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 void *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 8e4f6864e5..e40566b238 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1253,7 +1253,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 void *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 0000000000..dc5654d31c --- /dev/null +++ b/src/include/access/options.h @@ -0,0 +1,262 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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; + +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 catalog + * 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, + char **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 21bde78ed0..93acc02cf7 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-2022, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -22,178 +17,40 @@ #include "access/amapi.h" #include "access/htup.h" #include "access/tupdesc.h" +#include "access/options.h" #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 +67,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 +76,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, char *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 5ad4f85940..99664a4114 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 eb56b1c6b8..d3ffe645c3 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -529,6 +529,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 void *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 03f14d6be1..493fa0ce5d 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -34,7 +34,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 67c30394c8..d715c7f2c1 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,7 +14,7 @@ #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" @@ -23,11 +23,7 @@ PG_MODULE_MAGIC; -/* parse table for fillRelOptions */ -relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -relopt_kind di_relopt_kind; +void _PG_init(void); typedef enum DummyAmEnum { @@ -47,7 +43,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -61,77 +57,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; +void *digetreloptspecset(void); + +void * +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. */ @@ -216,19 +217,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. */ @@ -306,7 +294,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = divacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -320,12 +307,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/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index b6aef6f654..72fee4176b 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -164,7 +164,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 @@ -183,6 +183,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 4252b0202f..fadce3384d 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -105,6 +105,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 -- diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 9683b0a88e..ed0db93d81 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3099,9 +3099,9 @@ amgettuple_function aminitparallelscan_function aminsert_function ammarkpos_function -amoptions_function amparallelrescan_function amproperty_function +amreloptspecset_function amrescan_function amrestrpos_function amvacuumcleanup_function @@ -3257,6 +3257,7 @@ file_type_t filehash_hash filehash_iterator filemap_t +fill_string_option fill_string_relopt finalize_primnode_context find_dependent_phvs_context @@ -3384,7 +3385,6 @@ libpq_source line_t lineno_t list_sort_comparator -local_relopt local_relopts local_source locale_t @@ -3442,6 +3442,18 @@ oidvector on_dsm_detach_callback on_exit_nicely_callback openssl_tls_init_hook_typ +opt_enum_elt_def +option_spec_basic +option_spec_bool +option_spec_enum +option_spec_int +option_spec_real +option_spec_string +option_type +option_value +option_value_postvalidate +option_value_status +options_spec_set ossl_EVP_cipher_func other output_type @@ -3553,6 +3565,7 @@ pointer polymorphic_actuals pos_trgm post_parse_analyze_hook_type +postprocess_bytea_options_function postprocess_result_function pqbool pqsigfunc @@ -3608,16 +3621,6 @@ registered_buffer regmatch_t regoff_t regproc -relopt_bool -relopt_enum -relopt_enum_elt_def -relopt_gen -relopt_int -relopt_kind -relopt_parse_elt -relopt_real -relopt_string -relopt_type relopt_value relopts_validator remoteConn @@ -3764,6 +3767,7 @@ uuid_sortsupport_state uuid_t va_list vacuumingOptions +validate_string_option validate_string_relopt varatt_expanded varattrib_1b -- 2.30.2 --nextPart7348732.KNHxNKMfV0-- --nextPart8177062.GDmbcLR26a Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmNn3zEACgkQPMbfuIHA GphJXgf7BAyXY3Jc3CLGLkegKTTDJ7ljhFycEmpCF1VnQDPnjQOEEwBtnOGpzcdg tlOMp11RVp8ZzNZhgnbJ+9MopJrVkovR7KEpXaLBd3JChGyEaNcGMiLLw4g6GoAm HOhn83sE/U6fFYI6JGR/4nL/eSJPbHatKEryws96t93pO3g4aA1iZcOhLcTDt7de 5E5rCfC16M36qnfMu5h7/G6f2X3iBPs5yfrQlgTiXyT+zlvyWIy1Ud2gTWu5jZPC Oq4RYqf6lZku64DPIOJJVq5WkiDtPwLmeyRkx3eG5WILhcibUf7gfW7gtbV9KqVA fa9AV3i8XGLNvb3clAn7EOIGDOokQQ== =37V+ -----END PGP SIGNATURE----- --nextPart8177062.GDmbcLR26a-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH 03f] New options engine @ 2022-11-20 06:07 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2022-11-20 06:07 UTC (permalink / raw) 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 +- src/backend/access/brin/brin.c | 47 +- src/backend/access/common/Makefile | 1 + src/backend/access/common/meson.build | 1 + src/backend/access/common/options.c | 1366 ++++++++++ src/backend/access/common/reloptions.c | 2227 ++++------------- 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 | 19 +- 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/bin/pg_basebackup/streamutil.c | 2 +- src/include/access/amapi.h | 9 +- src/include/access/brin.h | 2 + src/include/access/brin_internal.h | 2 + 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 | 262 ++ src/include/access/reloptions.h | 205 +- 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 +- src/test/regress/expected/reloptions.out | 13 +- src/test/regress/sql/reloptions.sql | 7 + src/tools/pgindent/typedefs.list | 28 +- 47 files changed, 2590 insertions(+), 2321 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 6d58e22813..ce7e5a22d1 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" @@ -206,7 +207,8 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *bloptions(Datum reloptions, bool validate); +extern void *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 a434cf93ef..522da7dc9f 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 "catalog/index.h" #include "commands/vacuum.h" @@ -34,52 +34,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->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; - amroutine->amoptions = bloptions; + amroutine->amreloptspecset = blrelopt_specset; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = blvalidate; @@ -154,6 +114,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. */ @@ -474,24 +443,37 @@ BloomInitMetapage(Relation index) UnlockReleaseBuffer(metaBuffer); } -/* - * Parse reloptions for bloom index, producing a BloomOptions struct. - */ -bytea * -bloptions(Datum reloptions, bool validate) +void * +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 04095a8f0e..eeeb83f886 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -1976,7 +1976,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 67821cd25b..b3ceb1580a 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -196,7 +196,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 fa80ee2a55..42848b35e1 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -71,7 +71,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 e89dc74a5e..af46f0a3ee 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/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 7e386250ae..dd8f6de8d4 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" @@ -41,7 +40,6 @@ #include "utils/memutils.h" #include "utils/rel.h" - /* * We use a BrinBuildState during initial construction of a BRIN index. * The running state is kept in a BrinMemTuple. @@ -120,7 +118,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -135,6 +132,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -962,23 +960,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. @@ -1790,3 +1771,29 @@ check_null_keys(BrinValues *bval, ScanKey *nullkeys, int nnullkeys) return true; } + +static options_spec_set *brin_relopt_specset = NULL; + +void * +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 b9aff0ccfd..78c9c5a8d2 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 857beaa32d..fe8c1274a4 100644 --- a/src/backend/access/common/meson.build +++ b/src/backend/access/common/meson.build @@ -4,6 +4,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 0000000000..c626df3505 --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1366 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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 therm "specification" instead of "definition" because therm + * "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 Vale 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 vales. 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 + offest. + */ + +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 Option 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. + * + * size_of_bytea - 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", 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 objec 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, char **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", 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", + 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 75b7344891..c1293637b4 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-2022, 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" @@ -36,6 +33,7 @@ #include "utils/guc.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "storage/bufmgr.h" /* * Contents of pg_class.reloptions @@ -93,389 +91,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_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}, @@ -489,17 +106,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}, @@ -507,225 +115,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_for_toast); +void oid_postvalidate(option_value *value); /* * init_local_reloptions @@ -735,9 +127,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); } /* @@ -751,112 +142,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 @@ -867,47 +152,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); } /* @@ -921,47 +167,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); } /* @@ -973,140 +180,26 @@ add_real_reloption(bits32 kinds, const char *name, const char *desc, 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; - } + 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); } /* @@ -1122,247 +215,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, - char *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); } /* @@ -1379,12 +233,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, @@ -1398,544 +253,326 @@ 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. + * + * Autovacuum related parameters can be set at ShareUpdateExclusiveLock + * since they are only used by the AV procs and don't change anything + * currently executing. * - * options is a reloption text array as constructed by transformRelOptions. - * kind specifies the family of options to be processed. + * Fillfactor can be set because it applies only to subsequent changes made to + * data blocks, as documented in heapio.c * - * 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. + * 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. * - * 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). + * 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. * - * 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. + * 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. */ -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++; - } - } +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); + + 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_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; } /* @@ -1946,157 +583,167 @@ 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; - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); + values = optionsTextArrayToRawValues(options); + values = optionsParseRawValues(values, relopts->spec_set, validate); + opts = optionsValuesToBytea(values, relopts->spec_set); - elems[i].optname = opt->option->name; - elems[i].opttype = opt->option->type; - elems[i].offset = opt->offset; + /* + * Kind of ugly conversion here for backward compatibility. Would be + * removed while moving opclass options to options.c API + */ - i++; - } + if (validate && relopts->validators) + { + int val_count = 0; + int i; + option_value *val_array; - vals = parseLocalRelOptions(relopts, options, validate); - opts = allocateReloptStruct(relopts->relopt_struct_size, vals, noptions); - fillRelOptions(opts, relopts->relopt_struct_size, vals, noptions, validate, - elems, noptions); + foreach(lc, values) + val_count++; + val_array = palloc(sizeof(option_value) * val_count); + + i = 0; + foreach(lc, values) + { + option_value *val = lfirst(lc); - foreach(lc, relopts->validators) - ((relopts_validator) lfirst(lc)) (opts, vals, noptions); + memcpy(&(val_array[i]), val, sizeof(option_value)); + i++; + } - if (elems) - pfree(elems); + foreach(lc, relopts->validators) + ((relopts_validator) lfirst(lc)) (opts, val_array, val_count); + 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; } /* @@ -2106,33 +753,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 6df7f2eaeb..6852cadcab 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" @@ -28,6 +28,7 @@ #include "utils/builtins.h" #include "utils/index_selfuncs.h" #include "utils/typcache.h" +#include "utils/guc.h" /* @@ -67,7 +68,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; - amroutine->amoptions = ginoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = ginvalidate; @@ -82,6 +82,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gingetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -604,21 +605,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 * @@ -705,3 +691,29 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build) END_CRIT_SECTION(); } + +static options_spec_set *gin_relopt_specset = NULL; + +void * +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 30069f139c..bdb81dc8e9 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -89,7 +89,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -104,6 +103,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 1532462317..624bf965b0 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 "catalog/pg_opclass.h" #include "common/pg_prng.h" #include "storage/indexfsm.h" @@ -917,20 +917,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. * @@ -1065,3 +1051,39 @@ gistGetFakeLSN(Relation rel) return GetFakeLSNForUnloggedRel(); } } + +/* 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; + +void * +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 c361509d68..fdd8ae684d 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -86,7 +86,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -101,6 +100,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index 32822dbb6b..c745c7316a 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 "storage/buf_internals.h" @@ -272,19 +272,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 */ @@ -620,3 +607,22 @@ _hash_kill_items(IndexScanDesc scan) else _hash_relbuf(rel, buf); } + +static options_spec_set *hash_relopt_specset = NULL; + +void * +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 b52eca8f38..f7b0927f0b 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -23,6 +23,7 @@ #include "access/relscan.h" #include "access/xlog.h" #include "access/xloginsert.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -125,7 +126,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -140,6 +140,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = btestimateparallelscan; amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1419,3 +1420,35 @@ btcanreturn(Relation index, int attno) { return true; } + +static options_spec_set *bt_relopt_specset = NULL; + +void * +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 ff260c393a..a08004c045 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" #include "access/relscan.h" #include "catalog/catalog.h" #include "commands/progress.h" @@ -2107,23 +2107,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 2c661fcf96..676cc9d4cd 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" @@ -73,7 +73,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -88,6 +87,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -732,22 +732,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). @@ -1347,3 +1331,24 @@ spgproperty(Oid index_oid, int attno, return true; } + +static options_spec_set *spgist_relopt_specset = NULL; + +void * +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 152c29b551..6e9d0f2460 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -90,6 +90,7 @@ create_ctas_internal(List *attrList, IntoClause *into) Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; ObjectAddress intoRelationAddr; + List *toastDefList; /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview = (into->viewQuery != NULL); @@ -124,14 +125,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 55b0be9e1d..aee92d9fee 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 91cee27743..a887abf423 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -19,6 +19,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" @@ -549,7 +550,7 @@ DefineIndex(Oid relationId, Form_pg_am accessMethodForm; IndexAmRoutine *amRoutine; bool amcanorder; - amoptions_function amoptions; + amreloptspecset_function amreloptspecsetfn; bool partitioned; bool safe_index; Datum reloptions; @@ -864,7 +865,7 @@ DefineIndex(Oid relationId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; pfree(amRoutine); ReleaseSysCache(tuple); @@ -878,10 +879,18 @@ DefineIndex(Oid relationId, /* * 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. @@ -2118,8 +2127,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, palloc0_array(Datum, indexInfo->ii_NumIndexAttrs); indexInfo->ii_OpclassOptions[attn] = - transformRelOptions((Datum) 0, attribute->opclassopts, - NULL, NULL, false, false); + optionsDefListToTextArray(attribute->opclassopts); } attn++; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index f006807852..32ecde8021 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -20,6 +20,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" @@ -676,7 +677,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -824,19 +824,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 */ + char *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 */ + char *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) @@ -4120,7 +4137,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); @@ -4162,7 +4179,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. @@ -4383,9 +4400,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; @@ -8194,12 +8216,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)); @@ -14178,12 +14201,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]; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + char *heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -14203,38 +14227,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), @@ -14244,11 +14286,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; @@ -14323,20 +14369,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 76e100b560..9838868756 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -339,10 +339,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 @@ -1058,11 +1057,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 56fcb8edf1..fabb70e8bd 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); @@ -235,7 +235,7 @@ GetUserMapping(Oid userid, Oid serverid) if (isnull) um->options = NIL; else - um->options = untransformRelOptions(datum); + um->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -272,7 +272,7 @@ GetForeignTable(Oid relid) if (isnull) ft->options = NIL; else - ft->options = untransformRelOptions(datum); + ft->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -305,7 +305,7 @@ GetForeignColumnOptions(Oid relid, AttrNumber attnum) if (isnull) options = NIL; else - options = untransformRelOptions(datum); + options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -513,7 +513,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -610,7 +610,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 487eb2041b..90fce581e7 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1767,7 +1767,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; @@ -1831,7 +1831,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 247d0816ad..54a0536307 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1163,6 +1163,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1186,15 +1187,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); @@ -1303,9 +1305,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 9e252a0891..cc3b910379 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" @@ -148,7 +149,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 bd6cd4e47b..71e1ac5765 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -462,7 +462,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -477,11 +477,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; @@ -492,7 +492,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 5609246c2f..affe4cc2b4 100644 --- a/src/backend/utils/cache/spccache.c +++ b/src/backend/utils/cache/spccache.c @@ -148,7 +148,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/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index 4ccb90c197..7643bd34a0 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -755,7 +755,7 @@ AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); - if (option_value != NULL) + if (option_value !=NULL) { size_t length = strlen(option_value); char *escaped_value = palloc(1 + 2 * length); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 1dc674d230..afb619f8c4 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -136,10 +136,6 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, double *indexCorrelation, double *indexPages); -/* parse index reloptions */ -typedef bytea *(*amoptions_function) (Datum reloptions, - bool validate); - /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, @@ -186,6 +182,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan); /* restore marked scan position */ typedef void (*amrestrpos_function) (IndexScanDesc scan); +/* get catalog of reloptions definitions */ +typedef void *(*amreloptspecset_function) (); + /* * Callback function signatures - for parallel index scans. */ @@ -263,7 +262,6 @@ typedef struct IndexAmRoutine amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; - amoptions_function amoptions; amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; @@ -275,6 +273,7 @@ typedef struct IndexAmRoutine amendscan_function amendscan; ammarkpos_function ammarkpos; /* can be NULL */ amrestrpos_function amrestrpos; /* can be NULL */ + amreloptspecset_function amreloptspecset; /* can be NULL */ /* interface functions to support parallel index scans */ amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ diff --git a/src/include/access/brin.h b/src/include/access/brin.h index 887fb0a553..0aaf118ea5 100644 --- a/src/include/access/brin.h +++ b/src/include/access/brin.h @@ -36,6 +36,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 2518660927..a7dbb8a5f8 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -14,6 +14,7 @@ #include "access/amapi.h" #include "storage/bufpage.h" #include "utils/typcache.h" +#include "access/options.h" /* @@ -108,6 +109,7 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bytea *brinoptions(Datum reloptions, bool validate); +extern void *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 145ec7743c..4a5a87d002 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -108,6 +108,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 void *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 093bf23443..5f1a2dbd92 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 */ @@ -478,7 +480,7 @@ extern void gistadjustmembers(Oid opfamilyoid, #define GIST_MIN_FILLFACTOR 10 #define GIST_DEFAULT_FILLFACTOR 90 -extern bytea *gistoptions(Datum reloptions, bool validate); +extern void *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 da372841c4..12ff5ffcbb 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, @@ -472,6 +471,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 void *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 8e4f6864e5..e40566b238 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1253,7 +1253,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 void *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 0000000000..dc5654d31c --- /dev/null +++ b/src/include/access/options.h @@ -0,0 +1,262 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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; + +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 catalog + * 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, + char **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 21bde78ed0..93acc02cf7 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-2022, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -22,178 +17,40 @@ #include "access/amapi.h" #include "access/htup.h" #include "access/tupdesc.h" +#include "access/options.h" #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 +67,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 +76,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, char *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 5ad4f85940..99664a4114 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 eb56b1c6b8..d3ffe645c3 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -529,6 +529,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 void *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 03f14d6be1..493fa0ce5d 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -34,7 +34,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 67c30394c8..d715c7f2c1 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,7 +14,7 @@ #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" @@ -23,11 +23,7 @@ PG_MODULE_MAGIC; -/* parse table for fillRelOptions */ -relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -relopt_kind di_relopt_kind; +void _PG_init(void); typedef enum DummyAmEnum { @@ -47,7 +43,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -61,77 +57,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; +void *digetreloptspecset(void); + +void * +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. */ @@ -216,19 +217,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. */ @@ -306,7 +294,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = divacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -320,12 +307,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/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index b6aef6f654..72fee4176b 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -164,7 +164,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 @@ -183,6 +183,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 4252b0202f..fadce3384d 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -105,6 +105,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 -- diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 245aea1dd1..4ca7f6b0d0 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3100,9 +3100,9 @@ amgettuple_function aminitparallelscan_function aminsert_function ammarkpos_function -amoptions_function amparallelrescan_function amproperty_function +amreloptspecset_function amrescan_function amrestrpos_function amvacuumcleanup_function @@ -3258,6 +3258,7 @@ file_type_t filehash_hash filehash_iterator filemap_t +fill_string_option fill_string_relopt finalize_primnode_context find_dependent_phvs_context @@ -3385,7 +3386,6 @@ libpq_source line_t lineno_t list_sort_comparator -local_relopt local_relopts local_source locale_t @@ -3443,6 +3443,18 @@ oidvector on_dsm_detach_callback on_exit_nicely_callback openssl_tls_init_hook_typ +opt_enum_elt_def +option_spec_basic +option_spec_bool +option_spec_enum +option_spec_int +option_spec_real +option_spec_string +option_type +option_value +option_value_postvalidate +option_value_status +options_spec_set ossl_EVP_cipher_func other output_type @@ -3554,6 +3566,7 @@ pointer polymorphic_actuals pos_trgm post_parse_analyze_hook_type +postprocess_bytea_options_function postprocess_result_function pqbool pqsigfunc @@ -3609,16 +3622,6 @@ registered_buffer regmatch_t regoff_t regproc -relopt_bool -relopt_enum -relopt_enum_elt_def -relopt_gen -relopt_int -relopt_kind -relopt_parse_elt -relopt_real -relopt_string -relopt_type relopt_value relopts_validator remoteConn @@ -3765,6 +3768,7 @@ uuid_sortsupport_state uuid_t va_list vacuumingOptions +validate_string_option validate_string_relopt varatt_expanded varattrib_1b -- 2.30.2 --nextPart278155926.bz7RyglMVz-- --nextPart1808004.f1NczyPXRS Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmN5xSIACgkQPMbfuIHA Gphv3gf8CVJuLLIOOfRLG/UoQfDy2P0MYMU05rMyNUpKCxsImnMj4Ps5T6kAlT8z 2AixAyQVokC2ZKn1Tpx1Gxo4gtJx74ERSsBZ6VN2Y2CubHE/Sx+zSmDIWCHjdRXA hu6jkNIY6cE54Z4lOgcOOkm/KvateVQkFy4JVa+2vXcXdn1IGnWQIkQ4Ac/bFW+A X/PwJmTkp6ZudycWXEURlGgqXXecmE8Ead5Fdrit912BZHBsA51JP22EZ2McgBXf 211hXgENNSadBOASfcTth4K/bzOkBQAJd1cur5BxqTRh9w6AgzFKFGwWprqqH4qF zXSJz7Vz0ELAOLXfcsDYMsfMB0sXtA== =0h0j -----END PGP SIGNATURE----- --nextPart1808004.f1NczyPXRS-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH 03f] New options engine @ 2022-11-20 06:07 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2022-11-20 06:07 UTC (permalink / raw) 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 +- src/backend/access/brin/brin.c | 47 +- src/backend/access/common/Makefile | 1 + src/backend/access/common/meson.build | 1 + src/backend/access/common/options.c | 1366 ++++++++++ src/backend/access/common/reloptions.c | 2227 ++++------------- 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 | 19 +- 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/bin/pg_basebackup/streamutil.c | 2 +- src/include/access/amapi.h | 9 +- src/include/access/brin.h | 2 + src/include/access/brin_internal.h | 2 + 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 | 262 ++ src/include/access/reloptions.h | 205 +- 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 +- src/test/regress/expected/reloptions.out | 13 +- src/test/regress/sql/reloptions.sql | 7 + src/tools/pgindent/typedefs.list | 28 +- 47 files changed, 2590 insertions(+), 2321 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 6d58e22813..ce7e5a22d1 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" @@ -206,7 +207,8 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *bloptions(Datum reloptions, bool validate); +extern void *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 a434cf93ef..522da7dc9f 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 "catalog/index.h" #include "commands/vacuum.h" @@ -34,52 +34,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->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; - amroutine->amoptions = bloptions; + amroutine->amreloptspecset = blrelopt_specset; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = blvalidate; @@ -154,6 +114,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. */ @@ -474,24 +443,37 @@ BloomInitMetapage(Relation index) UnlockReleaseBuffer(metaBuffer); } -/* - * Parse reloptions for bloom index, producing a BloomOptions struct. - */ -bytea * -bloptions(Datum reloptions, bool validate) +void * +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 04095a8f0e..eeeb83f886 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -1976,7 +1976,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 67821cd25b..b3ceb1580a 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -196,7 +196,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 fa80ee2a55..42848b35e1 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -71,7 +71,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 e89dc74a5e..af46f0a3ee 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/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 7e386250ae..dd8f6de8d4 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" @@ -41,7 +40,6 @@ #include "utils/memutils.h" #include "utils/rel.h" - /* * We use a BrinBuildState during initial construction of a BRIN index. * The running state is kept in a BrinMemTuple. @@ -120,7 +118,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -135,6 +132,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -962,23 +960,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. @@ -1790,3 +1771,29 @@ check_null_keys(BrinValues *bval, ScanKey *nullkeys, int nnullkeys) return true; } + +static options_spec_set *brin_relopt_specset = NULL; + +void * +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 b9aff0ccfd..78c9c5a8d2 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 857beaa32d..fe8c1274a4 100644 --- a/src/backend/access/common/meson.build +++ b/src/backend/access/common/meson.build @@ -4,6 +4,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 0000000000..c626df3505 --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1366 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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 therm "specification" instead of "definition" because therm + * "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 Vale 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 vales. 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 + offest. + */ + +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 Option 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. + * + * size_of_bytea - 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", 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 objec 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, char **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", 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", + 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 75b7344891..c1293637b4 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-2022, 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" @@ -36,6 +33,7 @@ #include "utils/guc.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "storage/bufmgr.h" /* * Contents of pg_class.reloptions @@ -93,389 +91,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_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}, @@ -489,17 +106,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}, @@ -507,225 +115,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_for_toast); +void oid_postvalidate(option_value *value); /* * init_local_reloptions @@ -735,9 +127,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); } /* @@ -751,112 +142,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 @@ -867,47 +152,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); } /* @@ -921,47 +167,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); } /* @@ -973,140 +180,26 @@ add_real_reloption(bits32 kinds, const char *name, const char *desc, 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; - } + 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); } /* @@ -1122,247 +215,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, - char *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); } /* @@ -1379,12 +233,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, @@ -1398,544 +253,326 @@ 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. + * + * Autovacuum related parameters can be set at ShareUpdateExclusiveLock + * since they are only used by the AV procs and don't change anything + * currently executing. * - * options is a reloption text array as constructed by transformRelOptions. - * kind specifies the family of options to be processed. + * Fillfactor can be set because it applies only to subsequent changes made to + * data blocks, as documented in heapio.c * - * 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. + * 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. * - * 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). + * 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. * - * 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. + * 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. */ -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++; - } - } +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); + + 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_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; } /* @@ -1946,157 +583,167 @@ 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; - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); + values = optionsTextArrayToRawValues(options); + values = optionsParseRawValues(values, relopts->spec_set, validate); + opts = optionsValuesToBytea(values, relopts->spec_set); - elems[i].optname = opt->option->name; - elems[i].opttype = opt->option->type; - elems[i].offset = opt->offset; + /* + * Kind of ugly conversion here for backward compatibility. Would be + * removed while moving opclass options to options.c API + */ - i++; - } + if (validate && relopts->validators) + { + int val_count = 0; + int i; + option_value *val_array; - vals = parseLocalRelOptions(relopts, options, validate); - opts = allocateReloptStruct(relopts->relopt_struct_size, vals, noptions); - fillRelOptions(opts, relopts->relopt_struct_size, vals, noptions, validate, - elems, noptions); + foreach(lc, values) + val_count++; + val_array = palloc(sizeof(option_value) * val_count); + + i = 0; + foreach(lc, values) + { + option_value *val = lfirst(lc); - foreach(lc, relopts->validators) - ((relopts_validator) lfirst(lc)) (opts, vals, noptions); + memcpy(&(val_array[i]), val, sizeof(option_value)); + i++; + } - if (elems) - pfree(elems); + foreach(lc, relopts->validators) + ((relopts_validator) lfirst(lc)) (opts, val_array, val_count); + 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; } /* @@ -2106,33 +753,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 6df7f2eaeb..6852cadcab 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" @@ -28,6 +28,7 @@ #include "utils/builtins.h" #include "utils/index_selfuncs.h" #include "utils/typcache.h" +#include "utils/guc.h" /* @@ -67,7 +68,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; - amroutine->amoptions = ginoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = ginvalidate; @@ -82,6 +82,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gingetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -604,21 +605,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 * @@ -705,3 +691,29 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build) END_CRIT_SECTION(); } + +static options_spec_set *gin_relopt_specset = NULL; + +void * +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 30069f139c..bdb81dc8e9 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -89,7 +89,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -104,6 +103,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 1532462317..624bf965b0 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 "catalog/pg_opclass.h" #include "common/pg_prng.h" #include "storage/indexfsm.h" @@ -917,20 +917,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. * @@ -1065,3 +1051,39 @@ gistGetFakeLSN(Relation rel) return GetFakeLSNForUnloggedRel(); } } + +/* 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; + +void * +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 c361509d68..fdd8ae684d 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -86,7 +86,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -101,6 +100,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index 32822dbb6b..c745c7316a 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 "storage/buf_internals.h" @@ -272,19 +272,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 */ @@ -620,3 +607,22 @@ _hash_kill_items(IndexScanDesc scan) else _hash_relbuf(rel, buf); } + +static options_spec_set *hash_relopt_specset = NULL; + +void * +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 b52eca8f38..f7b0927f0b 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -23,6 +23,7 @@ #include "access/relscan.h" #include "access/xlog.h" #include "access/xloginsert.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -125,7 +126,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -140,6 +140,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = btestimateparallelscan; amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1419,3 +1420,35 @@ btcanreturn(Relation index, int attno) { return true; } + +static options_spec_set *bt_relopt_specset = NULL; + +void * +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 ff260c393a..a08004c045 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" #include "access/relscan.h" #include "catalog/catalog.h" #include "commands/progress.h" @@ -2107,23 +2107,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 2c661fcf96..676cc9d4cd 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" @@ -73,7 +73,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -88,6 +87,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -732,22 +732,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). @@ -1347,3 +1331,24 @@ spgproperty(Oid index_oid, int attno, return true; } + +static options_spec_set *spgist_relopt_specset = NULL; + +void * +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 152c29b551..6e9d0f2460 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -90,6 +90,7 @@ create_ctas_internal(List *attrList, IntoClause *into) Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; ObjectAddress intoRelationAddr; + List *toastDefList; /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview = (into->viewQuery != NULL); @@ -124,14 +125,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 55b0be9e1d..aee92d9fee 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 91cee27743..a887abf423 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -19,6 +19,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" @@ -549,7 +550,7 @@ DefineIndex(Oid relationId, Form_pg_am accessMethodForm; IndexAmRoutine *amRoutine; bool amcanorder; - amoptions_function amoptions; + amreloptspecset_function amreloptspecsetfn; bool partitioned; bool safe_index; Datum reloptions; @@ -864,7 +865,7 @@ DefineIndex(Oid relationId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; pfree(amRoutine); ReleaseSysCache(tuple); @@ -878,10 +879,18 @@ DefineIndex(Oid relationId, /* * 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. @@ -2118,8 +2127,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, palloc0_array(Datum, indexInfo->ii_NumIndexAttrs); indexInfo->ii_OpclassOptions[attn] = - transformRelOptions((Datum) 0, attribute->opclassopts, - NULL, NULL, false, false); + optionsDefListToTextArray(attribute->opclassopts); } attn++; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index f006807852..32ecde8021 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -20,6 +20,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" @@ -676,7 +677,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -824,19 +824,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 */ + char *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 */ + char *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) @@ -4120,7 +4137,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); @@ -4162,7 +4179,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. @@ -4383,9 +4400,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; @@ -8194,12 +8216,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)); @@ -14178,12 +14201,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]; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + char *heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -14203,38 +14227,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), @@ -14244,11 +14286,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; @@ -14323,20 +14369,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 76e100b560..9838868756 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -339,10 +339,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 @@ -1058,11 +1057,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 56fcb8edf1..fabb70e8bd 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); @@ -235,7 +235,7 @@ GetUserMapping(Oid userid, Oid serverid) if (isnull) um->options = NIL; else - um->options = untransformRelOptions(datum); + um->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -272,7 +272,7 @@ GetForeignTable(Oid relid) if (isnull) ft->options = NIL; else - ft->options = untransformRelOptions(datum); + ft->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -305,7 +305,7 @@ GetForeignColumnOptions(Oid relid, AttrNumber attnum) if (isnull) options = NIL; else - options = untransformRelOptions(datum); + options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -513,7 +513,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -610,7 +610,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 487eb2041b..90fce581e7 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1767,7 +1767,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; @@ -1831,7 +1831,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 247d0816ad..54a0536307 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1163,6 +1163,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1186,15 +1187,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); @@ -1303,9 +1305,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 9e252a0891..cc3b910379 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" @@ -148,7 +149,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 bd6cd4e47b..71e1ac5765 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -462,7 +462,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -477,11 +477,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; @@ -492,7 +492,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 5609246c2f..affe4cc2b4 100644 --- a/src/backend/utils/cache/spccache.c +++ b/src/backend/utils/cache/spccache.c @@ -148,7 +148,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/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index 4ccb90c197..7643bd34a0 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -755,7 +755,7 @@ AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); - if (option_value != NULL) + if (option_value !=NULL) { size_t length = strlen(option_value); char *escaped_value = palloc(1 + 2 * length); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 1dc674d230..afb619f8c4 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -136,10 +136,6 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, double *indexCorrelation, double *indexPages); -/* parse index reloptions */ -typedef bytea *(*amoptions_function) (Datum reloptions, - bool validate); - /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, @@ -186,6 +182,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan); /* restore marked scan position */ typedef void (*amrestrpos_function) (IndexScanDesc scan); +/* get catalog of reloptions definitions */ +typedef void *(*amreloptspecset_function) (); + /* * Callback function signatures - for parallel index scans. */ @@ -263,7 +262,6 @@ typedef struct IndexAmRoutine amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; - amoptions_function amoptions; amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; @@ -275,6 +273,7 @@ typedef struct IndexAmRoutine amendscan_function amendscan; ammarkpos_function ammarkpos; /* can be NULL */ amrestrpos_function amrestrpos; /* can be NULL */ + amreloptspecset_function amreloptspecset; /* can be NULL */ /* interface functions to support parallel index scans */ amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ diff --git a/src/include/access/brin.h b/src/include/access/brin.h index 887fb0a553..0aaf118ea5 100644 --- a/src/include/access/brin.h +++ b/src/include/access/brin.h @@ -36,6 +36,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 2518660927..a7dbb8a5f8 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -14,6 +14,7 @@ #include "access/amapi.h" #include "storage/bufpage.h" #include "utils/typcache.h" +#include "access/options.h" /* @@ -108,6 +109,7 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bytea *brinoptions(Datum reloptions, bool validate); +extern void *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 145ec7743c..4a5a87d002 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -108,6 +108,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 void *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 093bf23443..5f1a2dbd92 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 */ @@ -478,7 +480,7 @@ extern void gistadjustmembers(Oid opfamilyoid, #define GIST_MIN_FILLFACTOR 10 #define GIST_DEFAULT_FILLFACTOR 90 -extern bytea *gistoptions(Datum reloptions, bool validate); +extern void *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 da372841c4..12ff5ffcbb 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, @@ -472,6 +471,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 void *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 8e4f6864e5..e40566b238 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1253,7 +1253,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 void *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 0000000000..dc5654d31c --- /dev/null +++ b/src/include/access/options.h @@ -0,0 +1,262 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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; + +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 catalog + * 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, + char **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 21bde78ed0..93acc02cf7 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-2022, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -22,178 +17,40 @@ #include "access/amapi.h" #include "access/htup.h" #include "access/tupdesc.h" +#include "access/options.h" #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 +67,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 +76,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, char *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 5ad4f85940..99664a4114 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 eb56b1c6b8..d3ffe645c3 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -529,6 +529,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 void *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 03f14d6be1..493fa0ce5d 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -34,7 +34,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 67c30394c8..d715c7f2c1 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,7 +14,7 @@ #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" @@ -23,11 +23,7 @@ PG_MODULE_MAGIC; -/* parse table for fillRelOptions */ -relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -relopt_kind di_relopt_kind; +void _PG_init(void); typedef enum DummyAmEnum { @@ -47,7 +43,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -61,77 +57,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; +void *digetreloptspecset(void); + +void * +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. */ @@ -216,19 +217,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. */ @@ -306,7 +294,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = divacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -320,12 +307,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/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index b6aef6f654..72fee4176b 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -164,7 +164,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 @@ -183,6 +183,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 4252b0202f..fadce3384d 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -105,6 +105,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 -- diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 245aea1dd1..4ca7f6b0d0 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3100,9 +3100,9 @@ amgettuple_function aminitparallelscan_function aminsert_function ammarkpos_function -amoptions_function amparallelrescan_function amproperty_function +amreloptspecset_function amrescan_function amrestrpos_function amvacuumcleanup_function @@ -3258,6 +3258,7 @@ file_type_t filehash_hash filehash_iterator filemap_t +fill_string_option fill_string_relopt finalize_primnode_context find_dependent_phvs_context @@ -3385,7 +3386,6 @@ libpq_source line_t lineno_t list_sort_comparator -local_relopt local_relopts local_source locale_t @@ -3443,6 +3443,18 @@ oidvector on_dsm_detach_callback on_exit_nicely_callback openssl_tls_init_hook_typ +opt_enum_elt_def +option_spec_basic +option_spec_bool +option_spec_enum +option_spec_int +option_spec_real +option_spec_string +option_type +option_value +option_value_postvalidate +option_value_status +options_spec_set ossl_EVP_cipher_func other output_type @@ -3554,6 +3566,7 @@ pointer polymorphic_actuals pos_trgm post_parse_analyze_hook_type +postprocess_bytea_options_function postprocess_result_function pqbool pqsigfunc @@ -3609,16 +3622,6 @@ registered_buffer regmatch_t regoff_t regproc -relopt_bool -relopt_enum -relopt_enum_elt_def -relopt_gen -relopt_int -relopt_kind -relopt_parse_elt -relopt_real -relopt_string -relopt_type relopt_value relopts_validator remoteConn @@ -3765,6 +3768,7 @@ uuid_sortsupport_state uuid_t va_list vacuumingOptions +validate_string_option validate_string_relopt varatt_expanded varattrib_1b -- 2.30.2 --nextPart278155926.bz7RyglMVz-- --nextPart1808004.f1NczyPXRS Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmN5xSIACgkQPMbfuIHA Gphv3gf8CVJuLLIOOfRLG/UoQfDy2P0MYMU05rMyNUpKCxsImnMj4Ps5T6kAlT8z 2AixAyQVokC2ZKn1Tpx1Gxo4gtJx74ERSsBZ6VN2Y2CubHE/Sx+zSmDIWCHjdRXA hu6jkNIY6cE54Z4lOgcOOkm/KvateVQkFy4JVa+2vXcXdn1IGnWQIkQ4Ac/bFW+A X/PwJmTkp6ZudycWXEURlGgqXXecmE8Ead5Fdrit912BZHBsA51JP22EZ2McgBXf 211hXgENNSadBOASfcTth4K/bzOkBQAJd1cur5BxqTRh9w6AgzFKFGwWprqqH4qF zXSJz7Vz0ELAOLXfcsDYMsfMB0sXtA== =0h0j -----END PGP SIGNATURE----- --nextPart1808004.f1NczyPXRS-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v4] New options engine @ 2023-02-01 08:52 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2023-02-01 08:52 UTC (permalink / raw) 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 +- src/backend/access/brin/brin.c | 47 +- src/backend/access/common/Makefile | 1 + src/backend/access/common/meson.build | 1 + src/backend/access/common/options.c | 1366 ++++++++++ src/backend/access/common/reloptions.c | 2235 ++++------------- 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 | 19 +- 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/bin/pg_basebackup/streamutil.c | 2 +- src/include/access/amapi.h | 9 +- src/include/access/brin.h | 2 + src/include/access/brin_internal.h | 2 + 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 | 262 ++ src/include/access/reloptions.h | 205 +- 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 | 138 +- src/test/regress/expected/reloptions.out | 13 +- src/test/regress/sql/reloptions.sql | 7 + src/tools/pgindent/typedefs.list | 28 +- 47 files changed, 2589 insertions(+), 2320 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 efdf9415d1..77c4fcea9b 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" @@ -206,7 +207,8 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *bloptions(Datum reloptions, bool validate); +extern void *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 a6d9f09f31..f50f276344 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 "catalog/index.h" #include "commands/vacuum.h" @@ -34,52 +34,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->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; - amroutine->amoptions = bloptions; + amroutine->amreloptspecset = blrelopt_specset; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = blvalidate; @@ -154,6 +114,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. */ @@ -474,24 +443,37 @@ BloomInitMetapage(Relation index) UnlockReleaseBuffer(metaBuffer); } -/* - * Parse reloptions for bloom index, producing a BloomOptions struct. - */ -bytea * -bloptions(Datum reloptions, bool validate) +void * +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 8dd122042b..d7483ccc8a 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -1976,7 +1976,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 8ccc167548..1e355b8f88 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -196,7 +196,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 984e4d168a..5729dae482 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -71,7 +71,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 e89dc74a5e..af46f0a3ee 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/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index de1427a1e0..591468a8a7 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" @@ -41,7 +40,6 @@ #include "utils/memutils.h" #include "utils/rel.h" - /* * We use a BrinBuildState during initial construction of a BRIN index. * The running state is kept in a BrinMemTuple. @@ -120,7 +118,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -135,6 +132,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -962,23 +960,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. @@ -1790,3 +1771,29 @@ check_null_keys(BrinValues *bval, ScanKey *nullkeys, int nnullkeys) return true; } + +static options_spec_set *brin_relopt_specset = NULL; + +void * +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 b9aff0ccfd..78c9c5a8d2 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 f5ac17b498..ef7d7619ef 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 0000000000..c626df3505 --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1366 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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 therm "specification" instead of "definition" because therm + * "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 Vale 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 vales. 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 + offest. + */ + +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 Option 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. + * + * size_of_bytea - 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", 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 objec 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, char **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", 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", + 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 14c23101ad..7730854e2c 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-2023, 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" @@ -36,6 +33,7 @@ #include "utils/guc.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "storage/bufmgr.h" /* * Contents of pg_class.reloptions @@ -93,389 +91,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_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}, @@ -489,17 +106,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}, @@ -507,225 +115,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_for_toast); +void oid_postvalidate(option_value *value); /* * init_local_reloptions @@ -735,9 +127,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); } /* @@ -751,112 +142,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 @@ -867,47 +152,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); } /* @@ -921,47 +167,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); } /* @@ -975,57 +182,9 @@ 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); + optionsSpecSetAddReal(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); - 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); } /* @@ -1036,77 +195,11 @@ add_enum_reloption(bits32 kinds, const char *name, const char *desc, */ void add_local_enum_reloption(local_relopts *relopts, const char *name, - const char *desc, relopt_enum_elt_def *members, + const char *desc, opt_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; - } - - return newoption; -} - -/* - * add_string_reloption - * Add a new string 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. - */ -void -add_string_reloption(bits32 kinds, const char *name, const char *desc, - const char *default_val, validate_string_relopt validator, - LOCKMODE lockmode) -{ - 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); } /* @@ -1122,247 +215,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, - char *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); } /* @@ -1379,12 +233,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, @@ -1398,544 +253,326 @@ 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); -} - -/* - * Interpret reloptions that are given in text-array format. - * - * options is a reloption text array as constructed by transformRelOptions. - * kind specifies the family of options to be processed. - * - * 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. - * - * 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). - * - * 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++; - } - } - } - - /* 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; - - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); - - values[i].gen = opt->option; - values[i].isset = false; - - i++; - } - - if (options != (Datum) 0) - parseRelOptionsInternal(options, validate, values, nopts); - - return values; -} - -/* - * 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) + if (value->values.bool_val) 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; - } - - if (parsed) - option->isset = true; - if (!nofree) - pfree(value); + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("tables declared WITH OIDS are not supported"))); } /* - * 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. + * Relation options and Lock levels: * - * "base" should be sizeof(struct) of the reloptions struct (StdRdOptions or - * equivalent). + * 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. + * + * Autovacuum related parameters can be set at ShareUpdateExclusiveLock + * since they are only used by the AV procs and don't change anything + * currently executing. + * + * Fillfactor can be set because it applies only to subsequent changes made to + * data blocks, as documented in heapio.c + * + * 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. + * + * 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. */ -static void * -allocateReloptStruct(Size base, relopt_value *options, int numoptions) + + +options_spec_set * +get_stdrd_relopt_spec_set(bool is_heap) { - Size size = base; - int i; + options_spec_set *stdrd_relopt_spec_set = allocateOptionsSpecSet( + is_heap ? NULL : "toast", sizeof(StdRdOptions), false, 0); - for (i = 0; i < numoptions; i++) + 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); + + 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) { - relopt_value *optval = &options[i]; + 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); - if (optval->gen->type == RELOPT_TYPE_STRING) - { - relopt_string *optstr = (relopt_string *) optval->gen; + 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); - if (optstr->fill_cb) - { - const char *val = optval->isset ? optval->values.string_val : - optstr->default_isnull ? NULL : optstr->default_val; + 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); - size += optstr->fill_cb(val, NULL); - } - else - size += GET_STRING_RELOPTION_LEN(*optval) + 1; - } + 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); } - return palloc0(size); + 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\".")); + + 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 (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 stdrd_relopt_spec_set; +} + + +static options_spec_set *heap_relopt_spec_set = NULL; + +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 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. + * 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 -fillRelOptions(void *rdopts, Size basesize, - relopt_value *options, int numoptions, - bool validate, - const relopt_parse_elt *elems, int numelems) +toast_options_postprocess(void *data, bool validate) { - int i; - int offset = basesize; - - for (i = 0; i < numoptions; i++) + if (data) { - int j; - bool found = false; + StdRdOptions *toast_options = (StdRdOptions *) data; - 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); + toast_options->fillfactor = 100; + toast_options->autovacuum.analyze_threshold = -1; + toast_options->autovacuum.analyze_scale_factor = -1; } - SET_VARSIZE(rdopts, offset); +} + +static options_spec_set *toast_relopt_spec_set = NULL; +options_spec_set * +get_toast_relopt_spec_set(void) +{ + if (toast_relopt_spec_set) + return toast_relopt_spec_set; + + toast_relopt_spec_set = get_stdrd_relopt_spec_set(false); + toast_relopt_spec_set->postprocess_fun = toast_options_postprocess; + + 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_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) + +static options_spec_set *partitioned_relopt_spec_set = NULL; + +options_spec_set * +get_partitioned_relopt_spec_set(void) { - int numoptions; - relopt_value *options; - void *rdopts; + if (partitioned_relopt_spec_set) + return partitioned_relopt_spec_set; + partitioned_relopt_spec_set = get_stdrd_relopt_spec_set(true); - /* parse options specific to given relation option kind */ - options = parseRelOptions(reloptions, validate, kind, &numoptions); - Assert(numoptions <= num_relopt_elems); + /* No options for now, so spec set is empty */ + partitioned_relopt_spec_set->postprocess_fun = + partitioned_options_postprocess; - /* if none set, we're done */ - if (numoptions == 0) - { - Assert(options == NULL); - return 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); - - pfree(options); - - return rdopts; + return partitioned_relopt_spec_set; } /* @@ -1946,157 +583,167 @@ 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; - foreach(lc, relopts->options) + 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 + */ + + if (validate && relopts->validators) { - local_relopt *opt = lfirst(lc); + int val_count = 0; + int i; + option_value *val_array; - elems[i].optname = opt->option->name; - elems[i].opttype = opt->option->type; - elems[i].offset = opt->offset; + foreach(lc, values) + val_count++; + val_array = palloc(sizeof(option_value) * val_count); - i++; + i = 0; + foreach(lc, values) + { + option_value *val = lfirst(lc); + + memcpy(&(val_array[i]), val, sizeof(option_value)); + i++; + } + + foreach(lc, relopts->validators) + ((relopts_validator) lfirst(lc)) (opts, val_array, val_count); + + pfree(val_array); } - - vals = parseLocalRelOptions(relopts, options, validate); - opts = allocateReloptStruct(relopts->relopt_struct_size, vals, noptions); - fillRelOptions(opts, relopts->relopt_struct_size, vals, noptions, validate, - elems, noptions); - - foreach(lc, relopts->validators) - ((relopts_validator) lfirst(lc)) (opts, vals, noptions); - - if (elems) - pfree(elems); - return opts; + } /* - * Option parser for partitioned tables + * get_view_relopt_spec_set + * Returns an options catalog for view relation. */ -bytea * -partitioned_table_reloptions(Datum reloptions, bool validate) +static options_spec_set *view_relopt_spec_set = NULL; + +options_spec_set * +get_view_relopt_spec_set(void) { - 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; + if (view_relopt_spec_set) + return view_relopt_spec_set; + + 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); + + 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); + + 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 view_relopt_spec_set; } /* - * Option parser for views + * get_attribute_options_spec_set + * Returns an options spec set for heap attributes */ -bytea * -view_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[] = { - {"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)} - }; + if (attribute_options_spec_set) + return attribute_options_spec_set; - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_VIEW, - sizeof(ViewOptions), - tab, lengthof(tab)); -} + attribute_options_spec_set = allocateOptionsSpecSet(NULL, + sizeof(AttributeOpts), false, 2); -/* - * Parse options for heaps, views and toast tables. - */ -bytea * -heap_reloptions(char relkind, Datum reloptions, bool validate) -{ - StdRdOptions *rdopts; + 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); - 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; - } + 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; } /* - * 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) + * 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) { - Assert(amoptions != NULL); + if (tablespace_options_spec_set) + return tablespace_options_spec_set; - /* Assume function is strict */ - if (!PointerIsValid(DatumGetPointer(reloptions))) - return NULL; + 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); - return amoptions(reloptions, validate); -} + 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); -/* - * Option parser for attribute reloptions - */ -bytea * -attribute_reloptions(Datum reloptions, bool validate) -{ - 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)} - }; + 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 + ); - return (bytea *) build_reloptions(reloptions, validate, - RELOPT_KIND_ATTRIBUTE, - sizeof(AttributeOpts), - tab, lengthof(tab)); -} - -/* - * Option parser for tablespace reloptions - */ -bytea * -tablespace_reloptions(Datum reloptions, bool validate) -{ - 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)); + 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; } /* @@ -2106,33 +753,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 e7cc452a8a..38123ab198 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" @@ -28,6 +28,7 @@ #include "utils/builtins.h" #include "utils/index_selfuncs.h" #include "utils/typcache.h" +#include "utils/guc.h" /* @@ -67,7 +68,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; - amroutine->amoptions = ginoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = ginvalidate; @@ -82,6 +82,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gingetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -604,21 +605,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 * @@ -705,3 +691,29 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build) END_CRIT_SECTION(); } + +static options_spec_set *gin_relopt_specset = NULL; + +void * +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 ba394f08f6..4166c138c6 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -89,7 +89,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -104,6 +103,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 56451fede1..9bf7e8a8f0 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 "catalog/pg_opclass.h" #include "common/pg_prng.h" #include "storage/indexfsm.h" @@ -917,20 +917,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. * @@ -1065,3 +1051,39 @@ gistGetFakeLSN(Relation rel) return GetFakeLSNForUnloggedRel(); } } + +/* 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; + +void * +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 eb258337d6..7f6a92beb1 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -86,7 +86,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -101,6 +100,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index 88089ce02b..908835a7c9 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 "storage/buf_internals.h" @@ -272,19 +272,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 */ @@ -620,3 +607,22 @@ _hash_kill_items(IndexScanDesc scan) else _hash_relbuf(rel, buf); } + +static options_spec_set *hash_relopt_specset = NULL; + +void * +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 1cc88da032..4453d6e655 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -23,6 +23,7 @@ #include "access/relscan.h" #include "access/xlog.h" #include "access/xloginsert.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -125,7 +126,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -140,6 +140,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = btestimateparallelscan; amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1419,3 +1420,35 @@ btcanreturn(Relation index, int attno) { return true; } + +static options_spec_set *bt_relopt_specset = NULL; + +void * +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 8003583c0a..8823b56c26 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" #include "access/relscan.h" #include "catalog/catalog.h" #include "commands/progress.h" @@ -2107,23 +2107,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 3761f2c193..4203e174f8 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" @@ -73,7 +73,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -88,6 +87,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -732,22 +732,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). @@ -1347,3 +1331,24 @@ spgproperty(Oid index_oid, int attno, return true; } + +static options_spec_set *spgist_relopt_specset = NULL; + +void * +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 d6c6d514f3..e3fcb3f9db 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -90,6 +90,7 @@ create_ctas_internal(List *attrList, IntoClause *into) Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; ObjectAddress intoRelationAddr; + List *toastDefList; /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview = (into->viewQuery != NULL); @@ -124,14 +125,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 0ecff545a9..3d2cde76fe 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 16ec0b114e..760c3740b5 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -19,6 +19,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" @@ -550,7 +551,7 @@ DefineIndex(Oid relationId, Form_pg_am accessMethodForm; IndexAmRoutine *amRoutine; bool amcanorder; - amoptions_function amoptions; + amreloptspecset_function amreloptspecsetfn; bool partitioned; bool safe_index; Datum reloptions; @@ -865,7 +866,7 @@ DefineIndex(Oid relationId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; pfree(amRoutine); ReleaseSysCache(tuple); @@ -879,10 +880,18 @@ DefineIndex(Oid relationId, /* * 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. @@ -2120,8 +2129,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, palloc0_array(Datum, indexInfo->ii_NumIndexAttrs); indexInfo->ii_OpclassOptions[attn] = - transformRelOptions((Datum) 0, attribute->opclassopts, - NULL, NULL, false, false); + optionsDefListToTextArray(attribute->opclassopts); } attn++; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 7c697a285b..90078266cd 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -20,6 +20,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" @@ -676,7 +677,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -824,19 +824,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 */ + char *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 */ + char *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) @@ -4134,7 +4151,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); @@ -4176,7 +4193,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. @@ -4396,9 +4413,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; @@ -8168,12 +8190,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)); @@ -14157,12 +14180,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]; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + char *heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -14182,38 +14206,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), @@ -14223,11 +14265,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; @@ -14302,20 +14348,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 3dfbf6a917..38a25d39a4 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -339,10 +339,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 @@ -1058,11 +1057,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 dca02271dc..a2d866468e 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); @@ -235,7 +235,7 @@ GetUserMapping(Oid userid, Oid serverid) if (isnull) um->options = NIL; else - um->options = untransformRelOptions(datum); + um->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -272,7 +272,7 @@ GetForeignTable(Oid relid) if (isnull) ft->options = NIL; else - ft->options = untransformRelOptions(datum); + ft->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -305,7 +305,7 @@ GetForeignColumnOptions(Oid relid, AttrNumber attnum) if (isnull) options = NIL; else - options = untransformRelOptions(datum); + options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -513,7 +513,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -610,7 +610,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 f9218f48aa..6a0402af4f 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1763,7 +1763,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; @@ -1827,7 +1827,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 c7d9d96b45..172a15d1c0 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1163,6 +1163,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1186,15 +1187,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); @@ -1303,9 +1305,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 28a99f0fc4..c6859384e3 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" @@ -149,7 +150,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 d171cfcf2f..40be2dfe2a 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -462,7 +462,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -477,11 +477,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; @@ -492,7 +492,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 aabe6ba64b..0bc4fa3752 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/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index e7618f4617..867cd35cb3 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -755,7 +755,7 @@ AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); - if (option_value != NULL) + if (option_value !=NULL) { size_t length = strlen(option_value); char *escaped_value = palloc(1 + 2 * length); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 4f1f67b4d0..412dd6bbe5 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -136,10 +136,6 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, double *indexCorrelation, double *indexPages); -/* parse index reloptions */ -typedef bytea *(*amoptions_function) (Datum reloptions, - bool validate); - /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, @@ -186,6 +182,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan); /* restore marked scan position */ typedef void (*amrestrpos_function) (IndexScanDesc scan); +/* get catalog of reloptions definitions */ +typedef void *(*amreloptspecset_function) (); + /* * Callback function signatures - for parallel index scans. */ @@ -263,7 +262,6 @@ typedef struct IndexAmRoutine amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; - amoptions_function amoptions; amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; @@ -275,6 +273,7 @@ typedef struct IndexAmRoutine amendscan_function amendscan; ammarkpos_function ammarkpos; /* can be NULL */ amrestrpos_function amrestrpos; /* can be NULL */ + amreloptspecset_function amreloptspecset; /* can be NULL */ /* interface functions to support parallel index scans */ amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ diff --git a/src/include/access/brin.h b/src/include/access/brin.h index ed66f1b3d5..55ce930e0e 100644 --- a/src/include/access/brin.h +++ b/src/include/access/brin.h @@ -36,6 +36,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 97ddc925b2..e457f66c41 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -14,6 +14,7 @@ #include "access/amapi.h" #include "storage/bufpage.h" #include "utils/typcache.h" +#include "access/options.h" /* @@ -108,6 +109,7 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bytea *brinoptions(Datum reloptions, bool validate); +extern void *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 6da64928b6..5918f8976c 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -108,6 +108,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 void *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 8af33d7b40..4610281e8e 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 */ @@ -478,7 +480,7 @@ extern void gistadjustmembers(Oid opfamilyoid, #define GIST_MIN_FILLFACTOR 10 #define GIST_DEFAULT_FILLFACTOR 90 -extern bytea *gistoptions(Datum reloptions, bool validate); +extern void *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 9e035270a1..2ea2152044 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, @@ -474,6 +473,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 void *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 8f48960f9d..2503b52613 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1260,7 +1260,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 void *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 0000000000..dc5654d31c --- /dev/null +++ b/src/include/access/options.h @@ -0,0 +1,262 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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; + +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 catalog + * 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, + char **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 1d5bfa62ff..52f285dde4 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-2023, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -22,178 +17,40 @@ #include "access/amapi.h" #include "access/htup.h" #include "access/tupdesc.h" +#include "access/options.h" #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; +/* + * backward compatibility aliases so local reloption code of custom validator + * can work. + */ +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) -/* 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. + * relopts_validator functions is left for backward compatibility for using + * with local reloptions. Should not be used elsewhere */ -typedef struct relopt_enum_elt_def -{ - const char *string_val; - int symbol_val; -} relopt_enum_elt_def; - -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); /* 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 +67,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 +76,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, char *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 fe31d32dbe..0501ad600a 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 c6ef46fc20..2836f6079b 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -529,6 +529,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 void *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 e7c2b91a58..a971ab426d 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -34,7 +34,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 dfb1ebb846..fbec560556 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,7 +14,7 @@ #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" @@ -23,11 +23,7 @@ PG_MODULE_MAGIC; -/* parse table for fillRelOptions */ -relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -relopt_kind di_relopt_kind; +void _PG_init(void); typedef enum DummyAmEnum { @@ -47,7 +43,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -61,77 +57,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; +void *digetreloptspecset(void); + +void * +digetreloptspecset(void) { - di_relopt_kind = add_reloption_kind(); + if (di_relopt_specset) + return di_relopt_specset; - 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); + di_relopt_specset = allocateOptionsSpecSet(NULL, + sizeof(DummyIndexOptions), false, 6); - 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); + optionsSpecSetAddInt( + di_relopt_specset, "option_int", + "Integer option for dummy_index_am", + AccessExclusiveLock, + offsetof(DummyIndexOptions, option_int), NULL, + 10, -10, 100 + ); - 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); + optionsSpecSetAddReal( + di_relopt_specset, "option_real", + "Real option for dummy_index_am", + AccessExclusiveLock, + offsetof(DummyIndexOptions, option_real), NULL, + 3.1415, -10, 100 + ); - 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); + 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. */ @@ -216,19 +217,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. */ @@ -306,7 +294,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = divacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -320,12 +307,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/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index b6aef6f654..72fee4176b 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -164,7 +164,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 @@ -183,6 +183,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 4252b0202f..fadce3384d 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -105,6 +105,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 -- diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 24510ac29e..35d0f4e090 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3105,9 +3105,9 @@ amgettuple_function aminitparallelscan_function aminsert_function ammarkpos_function -amoptions_function amparallelrescan_function amproperty_function +amreloptspecset_function amrescan_function amrestrpos_function amvacuumcleanup_function @@ -3263,6 +3263,7 @@ file_type_t filehash_hash filehash_iterator filemap_t +fill_string_option fill_string_relopt finalize_primnode_context find_dependent_phvs_context @@ -3391,7 +3392,6 @@ libpq_source line_t lineno_t list_sort_comparator -local_relopt local_relopts local_source locale_t @@ -3449,6 +3449,18 @@ oidvector on_dsm_detach_callback on_exit_nicely_callback openssl_tls_init_hook_typ +opt_enum_elt_def +option_spec_basic +option_spec_bool +option_spec_enum +option_spec_int +option_spec_real +option_spec_string +option_type +option_value +option_value_postvalidate +option_value_status +options_spec_set ossl_EVP_cipher_func other output_type @@ -3560,6 +3572,7 @@ pointer polymorphic_actuals pos_trgm post_parse_analyze_hook_type +postprocess_bytea_options_function postprocess_result_function pqbool pqsigfunc @@ -3615,16 +3628,6 @@ registered_buffer regmatch_t regoff_t regproc -relopt_bool -relopt_enum -relopt_enum_elt_def -relopt_gen -relopt_int -relopt_kind -relopt_parse_elt -relopt_real -relopt_string -relopt_type relopt_value relopts_validator remoteConn @@ -3772,6 +3775,7 @@ uuid_sortsupport_state uuid_t va_list vacuumingOptions +validate_string_option validate_string_relopt varatt_expanded varattrib_1b -- 2.30.2 --gscolwndillnls27-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v6] New options engine @ 2024-02-01 06:29 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2024-02-01 06:29 UTC (permalink / raw) 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 +- 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 | 1366 ++++++++++ src/backend/access/common/reloptions.c | 2234 ++++------------- 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 | 19 +- 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/bin/pg_basebackup/streamutil.c | 2 +- src/include/access/amapi.h | 9 +- src/include/access/brin.h | 2 + src/include/access/brin_internal.h | 2 + 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 | 262 ++ src/include/access/reloptions.h | 205 +- 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 +- src/test/regress/expected/reloptions.out | 13 +- src/test/regress/sql/reloptions.sql | 7 + src/tools/pgindent/typedefs.list | 28 +- 47 files changed, 2592 insertions(+), 2325 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 fba3ba7771..8e6741bc74 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" @@ -206,7 +207,8 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *bloptions(Datum reloptions, bool validate); +extern void *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 6836129c90..5a7074f431 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 "catalog/index.h" #include "commands/vacuum.h" @@ -34,52 +34,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. */ @@ -137,7 +97,7 @@ blhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; - 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) +void * +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 19a362526d..054c1789d7 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -1941,7 +1941,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 249d82d3a0..26740d3ef7 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -197,7 +197,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 630b304338..e6df6dd53a 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -71,7 +71,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 517f20bc37..71581fba34 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/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 1087a9011e..4c51f22943 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" @@ -277,7 +276,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -292,6 +290,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1326,23 +1325,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. @@ -2933,3 +2915,29 @@ brin_fill_empty_ranges(BrinBuildState *state, blkno += state->bs_pagesPerRange; } } + +static options_spec_set *brin_relopt_specset = NULL; + +void * +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 b9aff0ccfd..78c9c5a8d2 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 725041a4ce..68467d5c4d 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 0000000000..c626df3505 --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1366 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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 therm "specification" instead of "definition" because therm + * "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 Vale 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 vales. 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 + offest. + */ + +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 Option 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. + * + * size_of_bytea - 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", 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 objec 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, char **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", 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", + 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 0921a736ab..fa3bf7c4de 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-2024, 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" @@ -36,6 +33,7 @@ #include "utils/guc.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "storage/bufmgr.h" /* * Contents of pg_class.reloptions @@ -93,389 +91,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_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}, @@ -489,17 +106,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}, @@ -507,225 +115,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_for_toast); +void oid_postvalidate(option_value *value); /* * init_local_reloptions @@ -735,9 +127,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); } /* @@ -751,112 +142,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 @@ -867,47 +152,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); } /* @@ -921,47 +167,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); } /* @@ -971,142 +178,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); } /* @@ -1122,247 +215,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, - char *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); } /* @@ -1379,12 +233,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, @@ -1398,544 +253,326 @@ 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); + + 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_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; } /* @@ -1946,158 +583,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; } /* @@ -2107,33 +751,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 6d05e7bdcd..1c74cf60b6 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" @@ -28,6 +28,7 @@ #include "utils/builtins.h" #include "utils/index_selfuncs.h" #include "utils/typcache.h" +#include "utils/guc.h" /* @@ -70,7 +71,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; - 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; + +void * +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 943ae91019..e169cb10a0 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -92,7 +92,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -107,6 +106,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 0057044513..530658cdd5 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 "catalog/pg_opclass.h" #include "common/pg_prng.h" #include "storage/indexfsm.h" @@ -909,20 +909,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. * @@ -1099,3 +1085,39 @@ GistTranslateStratnum(Oid opclass, StrategyNumber strat) result = OidFunctionCall1Coll(funcid, InvalidOid, UInt16GetDatum(strat)); 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; + +void * +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 fa5b59a150..889143c307 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -89,7 +89,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -104,6 +103,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index 2c68480852..fde36e140a 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 "storage/buf_internals.h" @@ -272,19 +272,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 */ @@ -620,3 +607,22 @@ _hash_kill_items(IndexScanDesc scan) else _hash_relbuf(rel, buf); } + +static options_spec_set *hash_relopt_specset = NULL; + +void * +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 696d79c085..64072e07a7 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -23,6 +23,7 @@ #include "access/relscan.h" #include "access/xlog.h" #include "access/xloginsert.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -128,7 +129,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -143,6 +143,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = btestimateparallelscan; amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1428,3 +1429,35 @@ btcanreturn(Relation index, int attno) { return true; } + +static options_spec_set *bt_relopt_specset = NULL; + +void * +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 2e6fc14d7a..49a380dc44 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" #include "access/relscan.h" #include "catalog/catalog.h" #include "commands/progress.h" @@ -2159,23 +2159,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 5b5e6e82d3..6c1ea3d936 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" @@ -76,7 +76,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -91,6 +90,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -733,22 +733,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). @@ -1348,3 +1332,24 @@ spgproperty(Oid index_oid, int attno, return true; } + +static options_spec_set *spgist_relopt_specset = NULL; + +void * +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 16a2fe65e6..780b499bcc 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -90,6 +90,7 @@ create_ctas_internal(List *attrList, IntoClause *into) Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; ObjectAddress intoRelationAddr; + List *toastDefList; /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview = (into->viewQuery != NULL); @@ -124,14 +125,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 cf61bbac1f..556cad13ed 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 7a87626f5f..f710d8b730 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" @@ -562,7 +563,7 @@ DefineIndex(Oid tableId, IndexAmRoutine *amRoutine; bool amcanorder; bool amissummarizing; - amoptions_function amoptions; + amreloptspecset_function amreloptspecsetfn; bool exclusion; bool partitioned; bool safe_index; @@ -878,7 +879,7 @@ DefineIndex(Oid tableId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; amissummarizing = amRoutine->amsummarizing; pfree(amRoutine); @@ -893,10 +894,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. @@ -2229,8 +2238,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 9f51696740..db19eb0c72 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -20,6 +20,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" @@ -702,7 +703,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -850,19 +850,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 */ + char *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 */ + char *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) @@ -4451,7 +4468,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); @@ -4493,7 +4510,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. @@ -4715,9 +4732,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; @@ -8880,12 +8902,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)); @@ -15254,12 +15277,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]; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + char *heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -15279,38 +15303,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), @@ -15320,11 +15362,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; @@ -15399,20 +15445,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 76ad66e51f..ae4a427bd3 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -339,10 +339,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 @@ -1058,11 +1057,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 02e1898131..7f94d9034b 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); @@ -517,7 +517,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -615,7 +615,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 56ac4f516e..073243869d 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1887,7 +1887,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; @@ -1951,7 +1951,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 8de821f960..39b2c5f14a 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1163,6 +1163,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1186,15 +1187,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); @@ -1303,9 +1305,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 af978ccd4b..b295191d12 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" @@ -149,7 +150,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 5ef8f2f0ca..0b2a3e15f4 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -464,7 +464,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -479,11 +479,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; @@ -494,7 +494,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 ec63cdc8e5..ea9552ca67 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/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index 56d1b15951..e9bf70c00c 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -763,7 +763,7 @@ AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); - if (option_value != NULL) + if (option_value !=NULL) { size_t length = strlen(option_value); char *escaped_value = palloc(1 + 2 * length); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 2c6c307efc..af108bbcd5 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -139,10 +139,6 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, double *indexCorrelation, double *indexPages); -/* parse index reloptions */ -typedef bytea *(*amoptions_function) (Datum reloptions, - bool validate); - /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, @@ -189,6 +185,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan); /* restore marked scan position */ typedef void (*amrestrpos_function) (IndexScanDesc scan); +/* get catalog of reloptions definitions */ +typedef void *(*amreloptspecset_function) (); + /* * Callback function signatures - for parallel index scans. */ @@ -271,7 +270,6 @@ typedef struct IndexAmRoutine amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; - amoptions_function amoptions; amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; @@ -283,6 +281,7 @@ typedef struct IndexAmRoutine amendscan_function amendscan; ammarkpos_function ammarkpos; /* can be NULL */ amrestrpos_function amrestrpos; /* can be NULL */ + amreloptspecset_function amreloptspecset; /* can be NULL */ /* interface functions to support parallel index scans */ amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ diff --git a/src/include/access/brin.h b/src/include/access/brin.h index 1c786281dd..a552003224 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 1c7eabe604..1c8d131939 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -14,6 +14,7 @@ #include "access/amapi.h" #include "storage/bufpage.h" #include "utils/typcache.h" +#include "access/options.h" /* @@ -109,6 +110,7 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bytea *brinoptions(Datum reloptions, bool validate); +extern void *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 51d0c74a6b..90bb631aaf 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -108,6 +108,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 void *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 7b8749c8db..4579f1b5ff 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 void *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 9c7d81525b..6fa680afe3 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, @@ -474,6 +473,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 void *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 6eb162052e..4d09ad267c 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1259,7 +1259,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 void *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 0000000000..dc5654d31c --- /dev/null +++ b/src/include/access/options.h @@ -0,0 +1,262 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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; + +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 catalog + * 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, + char **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 81829b8270..f80e1e840c 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-2024, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -22,178 +17,40 @@ #include "access/amapi.h" #include "access/htup.h" #include "access/tupdesc.h" +#include "access/options.h" #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 +67,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 +76,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, char *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 d6a4953120..7b113d7a76 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 2e9c757b30..a4d809cc29 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -529,6 +529,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 void *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 60d9b8f5b5..6040637c78 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 18185d0206..a8744fc415 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,7 +14,7 @@ #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" @@ -23,11 +23,7 @@ PG_MODULE_MAGIC; -/* parse table for fillRelOptions */ -relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -relopt_kind di_relopt_kind; +void _PG_init(void); typedef enum DummyAmEnum { @@ -47,7 +43,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -61,77 +57,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; +void *digetreloptspecset(void); + +void * +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. */ @@ -216,19 +217,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. */ @@ -308,7 +296,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = divacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -322,12 +309,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/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f..67f0a828f9 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 24fbe0b478..9b404295c0 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 -- diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 90b37b919c..c641002576 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3180,9 +3180,9 @@ amgettuple_function aminitparallelscan_function aminsert_function ammarkpos_function -amoptions_function amparallelrescan_function amproperty_function +amreloptspecset_function amrescan_function amrestrpos_function amvacuumcleanup_function @@ -3345,6 +3345,7 @@ file_type_t filehash_hash filehash_iterator filemap_t +fill_string_option fill_string_relopt finalize_primnode_context find_dependent_phvs_context @@ -3474,7 +3475,6 @@ libpq_source line_t lineno_t list_sort_comparator -local_relopt local_relopts local_source locale_t @@ -3534,6 +3534,18 @@ oidvector on_dsm_detach_callback on_exit_nicely_callback openssl_tls_init_hook_typ +opt_enum_elt_def +option_spec_basic +option_spec_bool +option_spec_enum +option_spec_int +option_spec_real +option_spec_string +option_type +option_value +option_value_postvalidate +option_value_status +options_spec_set ossl_EVP_cipher_func other output_type @@ -3648,6 +3660,7 @@ pointer polymorphic_actuals pos_trgm post_parse_analyze_hook_type +postprocess_bytea_options_function postprocess_result_function pqbool pqsigfunc @@ -3707,16 +3720,6 @@ registered_buffer regmatch_t regoff_t regproc -relopt_bool -relopt_enum -relopt_enum_elt_def -relopt_gen -relopt_int -relopt_kind -relopt_parse_elt -relopt_real -relopt_string -relopt_type relopt_value relopts_validator remoteConn @@ -3873,6 +3876,7 @@ uuid_sortsupport_state uuid_t va_list vacuumingOptions +validate_string_option validate_string_relopt varatt_expanded varattrib_1b -- 2.39.2 --nextPart5438179.Sb9uPGUboI-- --nextPart1966666.yKVeVyVuyW Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmW7QRgACgkQPMbfuIHA GpiC+wgAt1/7+AnopgINJGUG1VQMLPRkE6kEXyz7uy+JrkG03rIAHL19Qd9F6HPp RAg4cAbAqV0QSyDrg5p7ek73TRq3LV2ysTWdPvu5BIJy5JpGLYX6L4pjqUlTaQor JrYp6tSbqC4jIs1QJp8k7ewmnyWO37H8ySfnN1zw5VDUJbN0oVr+miZnKIVsYBh3 0WdktXc3hxX5TYfePqcaz37mntnP4VeqJ0+J0RLDLLzpvzgAQBEQmtQtt6SGoBnk ajpNa6b7VOmeCIpHG2l1qjCcNXd/pfJmjVyYiYkhZs8rFfsvltKJL8A1CDL7Ecz7 m5o2Vl74bl9/QzhsehWyH3TD6vQ/WA== =5JCC -----END PGP SIGNATURE----- --nextPart1966666.yKVeVyVuyW-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v6] New options engine @ 2024-02-01 06:29 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2024-02-01 06:29 UTC (permalink / raw) 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 +- 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 | 1366 ++++++++++ src/backend/access/common/reloptions.c | 2234 ++++------------- 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 | 19 +- 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/bin/pg_basebackup/streamutil.c | 2 +- src/include/access/amapi.h | 9 +- src/include/access/brin.h | 2 + src/include/access/brin_internal.h | 2 + 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 | 262 ++ src/include/access/reloptions.h | 205 +- 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 +- src/test/regress/expected/reloptions.out | 13 +- src/test/regress/sql/reloptions.sql | 7 + src/tools/pgindent/typedefs.list | 28 +- 47 files changed, 2592 insertions(+), 2325 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 fba3ba7771..8e6741bc74 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" @@ -206,7 +207,8 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *bloptions(Datum reloptions, bool validate); +extern void *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 6836129c90..5a7074f431 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 "catalog/index.h" #include "commands/vacuum.h" @@ -34,52 +34,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. */ @@ -137,7 +97,7 @@ blhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; - 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) +void * +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 19a362526d..054c1789d7 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -1941,7 +1941,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 249d82d3a0..26740d3ef7 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -197,7 +197,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 630b304338..e6df6dd53a 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -71,7 +71,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 517f20bc37..71581fba34 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/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 1087a9011e..4c51f22943 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" @@ -277,7 +276,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -292,6 +290,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1326,23 +1325,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. @@ -2933,3 +2915,29 @@ brin_fill_empty_ranges(BrinBuildState *state, blkno += state->bs_pagesPerRange; } } + +static options_spec_set *brin_relopt_specset = NULL; + +void * +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 b9aff0ccfd..78c9c5a8d2 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 725041a4ce..68467d5c4d 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 0000000000..c626df3505 --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1366 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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 therm "specification" instead of "definition" because therm + * "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 Vale 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 vales. 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 + offest. + */ + +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 Option 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. + * + * size_of_bytea - 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", 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 objec 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, char **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", 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", + 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 0921a736ab..fa3bf7c4de 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-2024, 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" @@ -36,6 +33,7 @@ #include "utils/guc.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "storage/bufmgr.h" /* * Contents of pg_class.reloptions @@ -93,389 +91,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_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}, @@ -489,17 +106,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}, @@ -507,225 +115,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_for_toast); +void oid_postvalidate(option_value *value); /* * init_local_reloptions @@ -735,9 +127,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); } /* @@ -751,112 +142,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 @@ -867,47 +152,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); } /* @@ -921,47 +167,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); } /* @@ -971,142 +178,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); } /* @@ -1122,247 +215,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, - char *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); } /* @@ -1379,12 +233,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, @@ -1398,544 +253,326 @@ 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); + + 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_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; } /* @@ -1946,158 +583,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; } /* @@ -2107,33 +751,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 6d05e7bdcd..1c74cf60b6 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" @@ -28,6 +28,7 @@ #include "utils/builtins.h" #include "utils/index_selfuncs.h" #include "utils/typcache.h" +#include "utils/guc.h" /* @@ -70,7 +71,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; - 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; + +void * +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 943ae91019..e169cb10a0 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -92,7 +92,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -107,6 +106,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 0057044513..530658cdd5 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 "catalog/pg_opclass.h" #include "common/pg_prng.h" #include "storage/indexfsm.h" @@ -909,20 +909,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. * @@ -1099,3 +1085,39 @@ GistTranslateStratnum(Oid opclass, StrategyNumber strat) result = OidFunctionCall1Coll(funcid, InvalidOid, UInt16GetDatum(strat)); 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; + +void * +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 fa5b59a150..889143c307 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -89,7 +89,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -104,6 +103,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index 2c68480852..fde36e140a 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 "storage/buf_internals.h" @@ -272,19 +272,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 */ @@ -620,3 +607,22 @@ _hash_kill_items(IndexScanDesc scan) else _hash_relbuf(rel, buf); } + +static options_spec_set *hash_relopt_specset = NULL; + +void * +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 696d79c085..64072e07a7 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -23,6 +23,7 @@ #include "access/relscan.h" #include "access/xlog.h" #include "access/xloginsert.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -128,7 +129,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -143,6 +143,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = btestimateparallelscan; amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1428,3 +1429,35 @@ btcanreturn(Relation index, int attno) { return true; } + +static options_spec_set *bt_relopt_specset = NULL; + +void * +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 2e6fc14d7a..49a380dc44 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" #include "access/relscan.h" #include "catalog/catalog.h" #include "commands/progress.h" @@ -2159,23 +2159,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 5b5e6e82d3..6c1ea3d936 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" @@ -76,7 +76,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -91,6 +90,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -733,22 +733,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). @@ -1348,3 +1332,24 @@ spgproperty(Oid index_oid, int attno, return true; } + +static options_spec_set *spgist_relopt_specset = NULL; + +void * +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 16a2fe65e6..780b499bcc 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -90,6 +90,7 @@ create_ctas_internal(List *attrList, IntoClause *into) Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; ObjectAddress intoRelationAddr; + List *toastDefList; /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview = (into->viewQuery != NULL); @@ -124,14 +125,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 cf61bbac1f..556cad13ed 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 7a87626f5f..f710d8b730 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" @@ -562,7 +563,7 @@ DefineIndex(Oid tableId, IndexAmRoutine *amRoutine; bool amcanorder; bool amissummarizing; - amoptions_function amoptions; + amreloptspecset_function amreloptspecsetfn; bool exclusion; bool partitioned; bool safe_index; @@ -878,7 +879,7 @@ DefineIndex(Oid tableId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; amissummarizing = amRoutine->amsummarizing; pfree(amRoutine); @@ -893,10 +894,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. @@ -2229,8 +2238,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 9f51696740..db19eb0c72 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -20,6 +20,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" @@ -702,7 +703,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -850,19 +850,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 */ + char *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 */ + char *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) @@ -4451,7 +4468,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); @@ -4493,7 +4510,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. @@ -4715,9 +4732,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; @@ -8880,12 +8902,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)); @@ -15254,12 +15277,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]; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + char *heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -15279,38 +15303,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), @@ -15320,11 +15362,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; @@ -15399,20 +15445,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 76ad66e51f..ae4a427bd3 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -339,10 +339,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 @@ -1058,11 +1057,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 02e1898131..7f94d9034b 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); @@ -517,7 +517,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -615,7 +615,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 56ac4f516e..073243869d 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1887,7 +1887,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; @@ -1951,7 +1951,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 8de821f960..39b2c5f14a 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1163,6 +1163,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1186,15 +1187,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); @@ -1303,9 +1305,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 af978ccd4b..b295191d12 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" @@ -149,7 +150,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 5ef8f2f0ca..0b2a3e15f4 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -464,7 +464,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -479,11 +479,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; @@ -494,7 +494,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 ec63cdc8e5..ea9552ca67 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/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index 56d1b15951..e9bf70c00c 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -763,7 +763,7 @@ AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); - if (option_value != NULL) + if (option_value !=NULL) { size_t length = strlen(option_value); char *escaped_value = palloc(1 + 2 * length); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 2c6c307efc..af108bbcd5 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -139,10 +139,6 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, double *indexCorrelation, double *indexPages); -/* parse index reloptions */ -typedef bytea *(*amoptions_function) (Datum reloptions, - bool validate); - /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, @@ -189,6 +185,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan); /* restore marked scan position */ typedef void (*amrestrpos_function) (IndexScanDesc scan); +/* get catalog of reloptions definitions */ +typedef void *(*amreloptspecset_function) (); + /* * Callback function signatures - for parallel index scans. */ @@ -271,7 +270,6 @@ typedef struct IndexAmRoutine amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; - amoptions_function amoptions; amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; @@ -283,6 +281,7 @@ typedef struct IndexAmRoutine amendscan_function amendscan; ammarkpos_function ammarkpos; /* can be NULL */ amrestrpos_function amrestrpos; /* can be NULL */ + amreloptspecset_function amreloptspecset; /* can be NULL */ /* interface functions to support parallel index scans */ amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ diff --git a/src/include/access/brin.h b/src/include/access/brin.h index 1c786281dd..a552003224 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 1c7eabe604..1c8d131939 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -14,6 +14,7 @@ #include "access/amapi.h" #include "storage/bufpage.h" #include "utils/typcache.h" +#include "access/options.h" /* @@ -109,6 +110,7 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bytea *brinoptions(Datum reloptions, bool validate); +extern void *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 51d0c74a6b..90bb631aaf 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -108,6 +108,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 void *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 7b8749c8db..4579f1b5ff 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 void *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 9c7d81525b..6fa680afe3 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, @@ -474,6 +473,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 void *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 6eb162052e..4d09ad267c 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1259,7 +1259,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 void *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 0000000000..dc5654d31c --- /dev/null +++ b/src/include/access/options.h @@ -0,0 +1,262 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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; + +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 catalog + * 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, + char **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 81829b8270..f80e1e840c 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-2024, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -22,178 +17,40 @@ #include "access/amapi.h" #include "access/htup.h" #include "access/tupdesc.h" +#include "access/options.h" #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 +67,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 +76,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, char *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 d6a4953120..7b113d7a76 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 2e9c757b30..a4d809cc29 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -529,6 +529,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 void *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 60d9b8f5b5..6040637c78 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 18185d0206..a8744fc415 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,7 +14,7 @@ #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" @@ -23,11 +23,7 @@ PG_MODULE_MAGIC; -/* parse table for fillRelOptions */ -relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -relopt_kind di_relopt_kind; +void _PG_init(void); typedef enum DummyAmEnum { @@ -47,7 +43,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -61,77 +57,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; +void *digetreloptspecset(void); + +void * +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. */ @@ -216,19 +217,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. */ @@ -308,7 +296,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = divacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -322,12 +309,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/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f..67f0a828f9 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 24fbe0b478..9b404295c0 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 -- diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 90b37b919c..c641002576 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3180,9 +3180,9 @@ amgettuple_function aminitparallelscan_function aminsert_function ammarkpos_function -amoptions_function amparallelrescan_function amproperty_function +amreloptspecset_function amrescan_function amrestrpos_function amvacuumcleanup_function @@ -3345,6 +3345,7 @@ file_type_t filehash_hash filehash_iterator filemap_t +fill_string_option fill_string_relopt finalize_primnode_context find_dependent_phvs_context @@ -3474,7 +3475,6 @@ libpq_source line_t lineno_t list_sort_comparator -local_relopt local_relopts local_source locale_t @@ -3534,6 +3534,18 @@ oidvector on_dsm_detach_callback on_exit_nicely_callback openssl_tls_init_hook_typ +opt_enum_elt_def +option_spec_basic +option_spec_bool +option_spec_enum +option_spec_int +option_spec_real +option_spec_string +option_type +option_value +option_value_postvalidate +option_value_status +options_spec_set ossl_EVP_cipher_func other output_type @@ -3648,6 +3660,7 @@ pointer polymorphic_actuals pos_trgm post_parse_analyze_hook_type +postprocess_bytea_options_function postprocess_result_function pqbool pqsigfunc @@ -3707,16 +3720,6 @@ registered_buffer regmatch_t regoff_t regproc -relopt_bool -relopt_enum -relopt_enum_elt_def -relopt_gen -relopt_int -relopt_kind -relopt_parse_elt -relopt_real -relopt_string -relopt_type relopt_value relopts_validator remoteConn @@ -3873,6 +3876,7 @@ uuid_sortsupport_state uuid_t va_list vacuumingOptions +validate_string_option validate_string_relopt varatt_expanded varattrib_1b -- 2.39.2 --nextPart5438179.Sb9uPGUboI-- --nextPart1966666.yKVeVyVuyW Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmW7QRgACgkQPMbfuIHA GpiC+wgAt1/7+AnopgINJGUG1VQMLPRkE6kEXyz7uy+JrkG03rIAHL19Qd9F6HPp RAg4cAbAqV0QSyDrg5p7ek73TRq3LV2ysTWdPvu5BIJy5JpGLYX6L4pjqUlTaQor JrYp6tSbqC4jIs1QJp8k7ewmnyWO37H8ySfnN1zw5VDUJbN0oVr+miZnKIVsYBh3 0WdktXc3hxX5TYfePqcaz37mntnP4VeqJ0+J0RLDLLzpvzgAQBEQmtQtt6SGoBnk ajpNa6b7VOmeCIpHG2l1qjCcNXd/pfJmjVyYiYkhZs8rFfsvltKJL8A1CDL7Ecz7 m5o2Vl74bl9/QzhsehWyH3TD6vQ/WA== =5JCC -----END PGP SIGNATURE----- --nextPart1966666.yKVeVyVuyW-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v7] New options engine @ 2024-02-07 06:36 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2024-02-07 06:36 UTC (permalink / raw) 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 +- 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 | 1366 ++++++++++ src/backend/access/common/reloptions.c | 2234 ++++------------- 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 | 19 +- 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/bin/pg_basebackup/streamutil.c | 2 +- src/include/access/amapi.h | 9 +- src/include/access/brin.h | 2 + src/include/access/brin_internal.h | 2 + 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 | 262 ++ src/include/access/reloptions.h | 205 +- 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 + src/tools/pgindent/typedefs.list | 28 +- 48 files changed, 2608 insertions(+), 2325 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 fba3ba7771..8e6741bc74 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" @@ -206,7 +207,8 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *bloptions(Datum reloptions, bool validate); +extern void *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 6836129c90..5a7074f431 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 "catalog/index.h" #include "commands/vacuum.h" @@ -34,52 +34,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. */ @@ -137,7 +97,7 @@ blhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; - 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) +void * +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 19a362526d..054c1789d7 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -1941,7 +1941,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 249d82d3a0..26740d3ef7 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -197,7 +197,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 630b304338..e6df6dd53a 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -71,7 +71,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 517f20bc37..71581fba34 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/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 1087a9011e..4c51f22943 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" @@ -277,7 +276,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -292,6 +290,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1326,23 +1325,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. @@ -2933,3 +2915,29 @@ brin_fill_empty_ranges(BrinBuildState *state, blkno += state->bs_pagesPerRange; } } + +static options_spec_set *brin_relopt_specset = NULL; + +void * +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 b9aff0ccfd..78c9c5a8d2 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 725041a4ce..68467d5c4d 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 0000000000..c626df3505 --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1366 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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 therm "specification" instead of "definition" because therm + * "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 Vale 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 vales. 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 + offest. + */ + +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 Option 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. + * + * size_of_bytea - 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", 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 objec 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, char **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", 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", + 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 0921a736ab..fa3bf7c4de 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-2024, 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" @@ -36,6 +33,7 @@ #include "utils/guc.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "storage/bufmgr.h" /* * Contents of pg_class.reloptions @@ -93,389 +91,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_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}, @@ -489,17 +106,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}, @@ -507,225 +115,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_for_toast); +void oid_postvalidate(option_value *value); /* * init_local_reloptions @@ -735,9 +127,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); } /* @@ -751,112 +142,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 @@ -867,47 +152,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); } /* @@ -921,47 +167,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); } /* @@ -971,142 +178,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); } /* @@ -1122,247 +215,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, - char *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); } /* @@ -1379,12 +233,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, @@ -1398,544 +253,326 @@ 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); + + 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_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; } /* @@ -1946,158 +583,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; } /* @@ -2107,33 +751,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 6d05e7bdcd..1c74cf60b6 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" @@ -28,6 +28,7 @@ #include "utils/builtins.h" #include "utils/index_selfuncs.h" #include "utils/typcache.h" +#include "utils/guc.h" /* @@ -70,7 +71,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; - 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; + +void * +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 943ae91019..e169cb10a0 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -92,7 +92,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -107,6 +106,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 0057044513..530658cdd5 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 "catalog/pg_opclass.h" #include "common/pg_prng.h" #include "storage/indexfsm.h" @@ -909,20 +909,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. * @@ -1099,3 +1085,39 @@ GistTranslateStratnum(Oid opclass, StrategyNumber strat) result = OidFunctionCall1Coll(funcid, InvalidOid, UInt16GetDatum(strat)); 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; + +void * +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 fa5b59a150..889143c307 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -89,7 +89,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -104,6 +103,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index 2c68480852..fde36e140a 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 "storage/buf_internals.h" @@ -272,19 +272,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 */ @@ -620,3 +607,22 @@ _hash_kill_items(IndexScanDesc scan) else _hash_relbuf(rel, buf); } + +static options_spec_set *hash_relopt_specset = NULL; + +void * +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 696d79c085..64072e07a7 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -23,6 +23,7 @@ #include "access/relscan.h" #include "access/xlog.h" #include "access/xloginsert.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -128,7 +129,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -143,6 +143,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = btestimateparallelscan; amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1428,3 +1429,35 @@ btcanreturn(Relation index, int attno) { return true; } + +static options_spec_set *bt_relopt_specset = NULL; + +void * +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 2e6fc14d7a..49a380dc44 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" #include "access/relscan.h" #include "catalog/catalog.h" #include "commands/progress.h" @@ -2159,23 +2159,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 5b5e6e82d3..6c1ea3d936 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" @@ -76,7 +76,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -91,6 +90,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -733,22 +733,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). @@ -1348,3 +1332,24 @@ spgproperty(Oid index_oid, int attno, return true; } + +static options_spec_set *spgist_relopt_specset = NULL; + +void * +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 16a2fe65e6..780b499bcc 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -90,6 +90,7 @@ create_ctas_internal(List *attrList, IntoClause *into) Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; ObjectAddress intoRelationAddr; + List *toastDefList; /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview = (into->viewQuery != NULL); @@ -124,14 +125,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 cf61bbac1f..556cad13ed 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 7a87626f5f..f710d8b730 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" @@ -562,7 +563,7 @@ DefineIndex(Oid tableId, IndexAmRoutine *amRoutine; bool amcanorder; bool amissummarizing; - amoptions_function amoptions; + amreloptspecset_function amreloptspecsetfn; bool exclusion; bool partitioned; bool safe_index; @@ -878,7 +879,7 @@ DefineIndex(Oid tableId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; amissummarizing = amRoutine->amsummarizing; pfree(amRoutine); @@ -893,10 +894,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. @@ -2229,8 +2238,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 9f51696740..db19eb0c72 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -20,6 +20,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" @@ -702,7 +703,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -850,19 +850,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 */ + char *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 */ + char *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) @@ -4451,7 +4468,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); @@ -4493,7 +4510,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. @@ -4715,9 +4732,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; @@ -8880,12 +8902,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)); @@ -15254,12 +15277,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]; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + char *heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -15279,38 +15303,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), @@ -15320,11 +15362,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; @@ -15399,20 +15445,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 76ad66e51f..ae4a427bd3 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -339,10 +339,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 @@ -1058,11 +1057,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 02e1898131..7f94d9034b 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); @@ -517,7 +517,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -615,7 +615,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 56ac4f516e..073243869d 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1887,7 +1887,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; @@ -1951,7 +1951,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 8de821f960..39b2c5f14a 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1163,6 +1163,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1186,15 +1187,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); @@ -1303,9 +1305,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 af978ccd4b..b295191d12 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" @@ -149,7 +150,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 ac106b40e3..95b57484c2 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -464,7 +464,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -479,11 +479,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; @@ -494,7 +494,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 ec63cdc8e5..ea9552ca67 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/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index 56d1b15951..e9bf70c00c 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -763,7 +763,7 @@ AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); - if (option_value != NULL) + if (option_value !=NULL) { size_t length = strlen(option_value); char *escaped_value = palloc(1 + 2 * length); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 2c6c307efc..af108bbcd5 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -139,10 +139,6 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, double *indexCorrelation, double *indexPages); -/* parse index reloptions */ -typedef bytea *(*amoptions_function) (Datum reloptions, - bool validate); - /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, @@ -189,6 +185,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan); /* restore marked scan position */ typedef void (*amrestrpos_function) (IndexScanDesc scan); +/* get catalog of reloptions definitions */ +typedef void *(*amreloptspecset_function) (); + /* * Callback function signatures - for parallel index scans. */ @@ -271,7 +270,6 @@ typedef struct IndexAmRoutine amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; - amoptions_function amoptions; amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; @@ -283,6 +281,7 @@ typedef struct IndexAmRoutine amendscan_function amendscan; ammarkpos_function ammarkpos; /* can be NULL */ amrestrpos_function amrestrpos; /* can be NULL */ + amreloptspecset_function amreloptspecset; /* can be NULL */ /* interface functions to support parallel index scans */ amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ diff --git a/src/include/access/brin.h b/src/include/access/brin.h index 1c786281dd..a552003224 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 1c7eabe604..1c8d131939 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -14,6 +14,7 @@ #include "access/amapi.h" #include "storage/bufpage.h" #include "utils/typcache.h" +#include "access/options.h" /* @@ -109,6 +110,7 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bytea *brinoptions(Datum reloptions, bool validate); +extern void *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 51d0c74a6b..90bb631aaf 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -108,6 +108,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 void *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 7b8749c8db..4579f1b5ff 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 void *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 9c7d81525b..6fa680afe3 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, @@ -474,6 +473,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 void *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 6eb162052e..4d09ad267c 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1259,7 +1259,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 void *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 0000000000..dc5654d31c --- /dev/null +++ b/src/include/access/options.h @@ -0,0 +1,262 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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; + +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 catalog + * 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, + char **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 81829b8270..f80e1e840c 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-2024, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -22,178 +17,40 @@ #include "access/amapi.h" #include "access/htup.h" #include "access/tupdesc.h" +#include "access/options.h" #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 +67,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 +76,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, char *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 d6a4953120..7b113d7a76 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 2e9c757b30..a4d809cc29 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -529,6 +529,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 void *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 60d9b8f5b5..6040637c78 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 18185d0206..a8744fc415 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,7 +14,7 @@ #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" @@ -23,11 +23,7 @@ PG_MODULE_MAGIC; -/* parse table for fillRelOptions */ -relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -relopt_kind di_relopt_kind; +void _PG_init(void); typedef enum DummyAmEnum { @@ -47,7 +43,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -61,77 +57,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; +void *digetreloptspecset(void); + +void * +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. */ @@ -216,19 +217,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. */ @@ -308,7 +296,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = divacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -322,12 +309,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 19adb28ffb..6544332b41 100644 --- a/src/test/modules/test_oat_hooks/expected/alter_table.out +++ b/src/test/modules/test_oat_hooks/expected/alter_table.out @@ -80,6 +80,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 @@ -87,6 +89,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 @@ -94,6 +98,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 @@ -101,6 +107,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 @@ -109,6 +117,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] @@ -118,6 +128,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] @@ -128,6 +140,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] @@ -137,6 +151,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 9de19b4e3f..67f0a828f9 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 24fbe0b478..9b404295c0 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 -- diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 91433d439b..fc28f5cac2 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3181,9 +3181,9 @@ amgettuple_function aminitparallelscan_function aminsert_function ammarkpos_function -amoptions_function amparallelrescan_function amproperty_function +amreloptspecset_function amrescan_function amrestrpos_function amvacuumcleanup_function @@ -3346,6 +3346,7 @@ file_type_t filehash_hash filehash_iterator filemap_t +fill_string_option fill_string_relopt finalize_primnode_context find_dependent_phvs_context @@ -3475,7 +3476,6 @@ libpq_source line_t lineno_t list_sort_comparator -local_relopt local_relopts local_source locale_t @@ -3535,6 +3535,18 @@ oidvector on_dsm_detach_callback on_exit_nicely_callback openssl_tls_init_hook_typ +opt_enum_elt_def +option_spec_basic +option_spec_bool +option_spec_enum +option_spec_int +option_spec_real +option_spec_string +option_type +option_value +option_value_postvalidate +option_value_status +options_spec_set ossl_EVP_cipher_func other output_type @@ -3649,6 +3661,7 @@ pointer polymorphic_actuals pos_trgm post_parse_analyze_hook_type +postprocess_bytea_options_function postprocess_result_function pqbool pqsigfunc @@ -3708,16 +3721,6 @@ registered_buffer regmatch_t regoff_t regproc -relopt_bool -relopt_enum -relopt_enum_elt_def -relopt_gen -relopt_int -relopt_kind -relopt_parse_elt -relopt_real -relopt_string -relopt_type relopt_value relopts_validator remoteConn @@ -3874,6 +3877,7 @@ uuid_sortsupport_state uuid_t va_list vacuumingOptions +validate_string_option validate_string_relopt varatt_expanded varattrib_1b -- 2.39.2 --nextPart4866644.GXAFRqVoOG-- --nextPart1876656.tdWV9SEqCh Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmXDJuQACgkQPMbfuIHA GpiFSgf+Ic0x+jDqw+OV9bXjMTuN26uYBKY976j9cFx5+JXBk0GIIlHyNtemX//T +BiZL+qSKgtxiGzxdkU7sB7innQRPiu5UuEeuezjXDALObl7nBpKeVfeEXnS5+vr VcH+XUFzC9BzvkP8woB5H6qwrEsk//oLHty9wePYhRpmeFdbQPeU2KMZS8bx033g Mlq3eoc4F+6R/S4IoycqTk4myp2lP5IPigZ7b4ip/60Gi/DjG7xMTYndkHcOKPSb om4DnpePldKefQ/hcLC+2HMuIuiZG57nwBrFl9Ho4WWGGEwG2RNpA/wAQf13gMdP uTZSvan/Ir+CSG/3VJcfa1d0rJhhsw== =v57d -----END PGP SIGNATURE----- --nextPart1876656.tdWV9SEqCh-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v7] New options engine @ 2024-02-07 06:36 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2024-02-07 06:36 UTC (permalink / raw) 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 +- 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 | 1366 ++++++++++ src/backend/access/common/reloptions.c | 2234 ++++------------- 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 | 19 +- 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/bin/pg_basebackup/streamutil.c | 2 +- src/include/access/amapi.h | 9 +- src/include/access/brin.h | 2 + src/include/access/brin_internal.h | 2 + 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 | 262 ++ src/include/access/reloptions.h | 205 +- 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 + src/tools/pgindent/typedefs.list | 28 +- 48 files changed, 2608 insertions(+), 2325 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 fba3ba7771..8e6741bc74 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" @@ -206,7 +207,8 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *bloptions(Datum reloptions, bool validate); +extern void *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 6836129c90..5a7074f431 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 "catalog/index.h" #include "commands/vacuum.h" @@ -34,52 +34,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. */ @@ -137,7 +97,7 @@ blhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; - 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) +void * +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 19a362526d..054c1789d7 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -1941,7 +1941,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 249d82d3a0..26740d3ef7 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -197,7 +197,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 630b304338..e6df6dd53a 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -71,7 +71,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 517f20bc37..71581fba34 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/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 1087a9011e..4c51f22943 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" @@ -277,7 +276,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -292,6 +290,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1326,23 +1325,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. @@ -2933,3 +2915,29 @@ brin_fill_empty_ranges(BrinBuildState *state, blkno += state->bs_pagesPerRange; } } + +static options_spec_set *brin_relopt_specset = NULL; + +void * +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 b9aff0ccfd..78c9c5a8d2 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 725041a4ce..68467d5c4d 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 0000000000..c626df3505 --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1366 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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 therm "specification" instead of "definition" because therm + * "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 Vale 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 vales. 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 + offest. + */ + +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 Option 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. + * + * size_of_bytea - 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", 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 objec 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, char **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", 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", + 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 0921a736ab..fa3bf7c4de 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-2024, 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" @@ -36,6 +33,7 @@ #include "utils/guc.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "storage/bufmgr.h" /* * Contents of pg_class.reloptions @@ -93,389 +91,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_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}, @@ -489,17 +106,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}, @@ -507,225 +115,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_for_toast); +void oid_postvalidate(option_value *value); /* * init_local_reloptions @@ -735,9 +127,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); } /* @@ -751,112 +142,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 @@ -867,47 +152,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); } /* @@ -921,47 +167,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); } /* @@ -971,142 +178,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); } /* @@ -1122,247 +215,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, - char *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); } /* @@ -1379,12 +233,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, @@ -1398,544 +253,326 @@ 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); + + 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_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; } /* @@ -1946,158 +583,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; } /* @@ -2107,33 +751,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 6d05e7bdcd..1c74cf60b6 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" @@ -28,6 +28,7 @@ #include "utils/builtins.h" #include "utils/index_selfuncs.h" #include "utils/typcache.h" +#include "utils/guc.h" /* @@ -70,7 +71,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; - 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; + +void * +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 943ae91019..e169cb10a0 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -92,7 +92,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -107,6 +106,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 0057044513..530658cdd5 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 "catalog/pg_opclass.h" #include "common/pg_prng.h" #include "storage/indexfsm.h" @@ -909,20 +909,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. * @@ -1099,3 +1085,39 @@ GistTranslateStratnum(Oid opclass, StrategyNumber strat) result = OidFunctionCall1Coll(funcid, InvalidOid, UInt16GetDatum(strat)); 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; + +void * +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 fa5b59a150..889143c307 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -89,7 +89,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -104,6 +103,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index 2c68480852..fde36e140a 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 "storage/buf_internals.h" @@ -272,19 +272,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 */ @@ -620,3 +607,22 @@ _hash_kill_items(IndexScanDesc scan) else _hash_relbuf(rel, buf); } + +static options_spec_set *hash_relopt_specset = NULL; + +void * +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 696d79c085..64072e07a7 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -23,6 +23,7 @@ #include "access/relscan.h" #include "access/xlog.h" #include "access/xloginsert.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -128,7 +129,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -143,6 +143,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = btestimateparallelscan; amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1428,3 +1429,35 @@ btcanreturn(Relation index, int attno) { return true; } + +static options_spec_set *bt_relopt_specset = NULL; + +void * +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 2e6fc14d7a..49a380dc44 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" #include "access/relscan.h" #include "catalog/catalog.h" #include "commands/progress.h" @@ -2159,23 +2159,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 5b5e6e82d3..6c1ea3d936 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" @@ -76,7 +76,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -91,6 +90,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -733,22 +733,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). @@ -1348,3 +1332,24 @@ spgproperty(Oid index_oid, int attno, return true; } + +static options_spec_set *spgist_relopt_specset = NULL; + +void * +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 16a2fe65e6..780b499bcc 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -90,6 +90,7 @@ create_ctas_internal(List *attrList, IntoClause *into) Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; ObjectAddress intoRelationAddr; + List *toastDefList; /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview = (into->viewQuery != NULL); @@ -124,14 +125,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 cf61bbac1f..556cad13ed 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 7a87626f5f..f710d8b730 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" @@ -562,7 +563,7 @@ DefineIndex(Oid tableId, IndexAmRoutine *amRoutine; bool amcanorder; bool amissummarizing; - amoptions_function amoptions; + amreloptspecset_function amreloptspecsetfn; bool exclusion; bool partitioned; bool safe_index; @@ -878,7 +879,7 @@ DefineIndex(Oid tableId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; amissummarizing = amRoutine->amsummarizing; pfree(amRoutine); @@ -893,10 +894,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. @@ -2229,8 +2238,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 9f51696740..db19eb0c72 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -20,6 +20,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" @@ -702,7 +703,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -850,19 +850,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 */ + char *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 */ + char *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) @@ -4451,7 +4468,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); @@ -4493,7 +4510,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. @@ -4715,9 +4732,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; @@ -8880,12 +8902,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)); @@ -15254,12 +15277,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]; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + char *heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -15279,38 +15303,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), @@ -15320,11 +15362,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; @@ -15399,20 +15445,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 76ad66e51f..ae4a427bd3 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -339,10 +339,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 @@ -1058,11 +1057,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 02e1898131..7f94d9034b 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); @@ -517,7 +517,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -615,7 +615,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 56ac4f516e..073243869d 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1887,7 +1887,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; @@ -1951,7 +1951,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 8de821f960..39b2c5f14a 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1163,6 +1163,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1186,15 +1187,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); @@ -1303,9 +1305,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 af978ccd4b..b295191d12 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" @@ -149,7 +150,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 ac106b40e3..95b57484c2 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -464,7 +464,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -479,11 +479,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; @@ -494,7 +494,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 ec63cdc8e5..ea9552ca67 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/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index 56d1b15951..e9bf70c00c 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -763,7 +763,7 @@ AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); - if (option_value != NULL) + if (option_value !=NULL) { size_t length = strlen(option_value); char *escaped_value = palloc(1 + 2 * length); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 2c6c307efc..af108bbcd5 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -139,10 +139,6 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, double *indexCorrelation, double *indexPages); -/* parse index reloptions */ -typedef bytea *(*amoptions_function) (Datum reloptions, - bool validate); - /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, @@ -189,6 +185,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan); /* restore marked scan position */ typedef void (*amrestrpos_function) (IndexScanDesc scan); +/* get catalog of reloptions definitions */ +typedef void *(*amreloptspecset_function) (); + /* * Callback function signatures - for parallel index scans. */ @@ -271,7 +270,6 @@ typedef struct IndexAmRoutine amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; - amoptions_function amoptions; amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; @@ -283,6 +281,7 @@ typedef struct IndexAmRoutine amendscan_function amendscan; ammarkpos_function ammarkpos; /* can be NULL */ amrestrpos_function amrestrpos; /* can be NULL */ + amreloptspecset_function amreloptspecset; /* can be NULL */ /* interface functions to support parallel index scans */ amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ diff --git a/src/include/access/brin.h b/src/include/access/brin.h index 1c786281dd..a552003224 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 1c7eabe604..1c8d131939 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -14,6 +14,7 @@ #include "access/amapi.h" #include "storage/bufpage.h" #include "utils/typcache.h" +#include "access/options.h" /* @@ -109,6 +110,7 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bytea *brinoptions(Datum reloptions, bool validate); +extern void *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 51d0c74a6b..90bb631aaf 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -108,6 +108,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 void *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 7b8749c8db..4579f1b5ff 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 void *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 9c7d81525b..6fa680afe3 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, @@ -474,6 +473,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 void *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 6eb162052e..4d09ad267c 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1259,7 +1259,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 void *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 0000000000..dc5654d31c --- /dev/null +++ b/src/include/access/options.h @@ -0,0 +1,262 @@ +/*------------------------------------------------------------------------- + * + * 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-2022, 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; + +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 catalog + * 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, + char **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 81829b8270..f80e1e840c 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-2024, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -22,178 +17,40 @@ #include "access/amapi.h" #include "access/htup.h" #include "access/tupdesc.h" +#include "access/options.h" #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 +67,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 +76,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, char *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 d6a4953120..7b113d7a76 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 2e9c757b30..a4d809cc29 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -529,6 +529,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 void *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 60d9b8f5b5..6040637c78 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 18185d0206..a8744fc415 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,7 +14,7 @@ #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" @@ -23,11 +23,7 @@ PG_MODULE_MAGIC; -/* parse table for fillRelOptions */ -relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -relopt_kind di_relopt_kind; +void _PG_init(void); typedef enum DummyAmEnum { @@ -47,7 +43,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -61,77 +57,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; +void *digetreloptspecset(void); + +void * +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. */ @@ -216,19 +217,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. */ @@ -308,7 +296,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = divacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -322,12 +309,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 19adb28ffb..6544332b41 100644 --- a/src/test/modules/test_oat_hooks/expected/alter_table.out +++ b/src/test/modules/test_oat_hooks/expected/alter_table.out @@ -80,6 +80,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 @@ -87,6 +89,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 @@ -94,6 +98,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 @@ -101,6 +107,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 @@ -109,6 +117,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] @@ -118,6 +128,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] @@ -128,6 +140,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] @@ -137,6 +151,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 9de19b4e3f..67f0a828f9 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 24fbe0b478..9b404295c0 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 -- diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 91433d439b..fc28f5cac2 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3181,9 +3181,9 @@ amgettuple_function aminitparallelscan_function aminsert_function ammarkpos_function -amoptions_function amparallelrescan_function amproperty_function +amreloptspecset_function amrescan_function amrestrpos_function amvacuumcleanup_function @@ -3346,6 +3346,7 @@ file_type_t filehash_hash filehash_iterator filemap_t +fill_string_option fill_string_relopt finalize_primnode_context find_dependent_phvs_context @@ -3475,7 +3476,6 @@ libpq_source line_t lineno_t list_sort_comparator -local_relopt local_relopts local_source locale_t @@ -3535,6 +3535,18 @@ oidvector on_dsm_detach_callback on_exit_nicely_callback openssl_tls_init_hook_typ +opt_enum_elt_def +option_spec_basic +option_spec_bool +option_spec_enum +option_spec_int +option_spec_real +option_spec_string +option_type +option_value +option_value_postvalidate +option_value_status +options_spec_set ossl_EVP_cipher_func other output_type @@ -3649,6 +3661,7 @@ pointer polymorphic_actuals pos_trgm post_parse_analyze_hook_type +postprocess_bytea_options_function postprocess_result_function pqbool pqsigfunc @@ -3708,16 +3721,6 @@ registered_buffer regmatch_t regoff_t regproc -relopt_bool -relopt_enum -relopt_enum_elt_def -relopt_gen -relopt_int -relopt_kind -relopt_parse_elt -relopt_real -relopt_string -relopt_type relopt_value relopts_validator remoteConn @@ -3874,6 +3877,7 @@ uuid_sortsupport_state uuid_t va_list vacuumingOptions +validate_string_option validate_string_relopt varatt_expanded varattrib_1b -- 2.39.2 --nextPart4866644.GXAFRqVoOG-- --nextPart1876656.tdWV9SEqCh Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmXDJuQACgkQPMbfuIHA GpiFSgf+Ic0x+jDqw+OV9bXjMTuN26uYBKY976j9cFx5+JXBk0GIIlHyNtemX//T +BiZL+qSKgtxiGzxdkU7sB7innQRPiu5UuEeuezjXDALObl7nBpKeVfeEXnS5+vr VcH+XUFzC9BzvkP8woB5H6qwrEsk//oLHty9wePYhRpmeFdbQPeU2KMZS8bx033g Mlq3eoc4F+6R/S4IoycqTk4myp2lP5IPigZ7b4ip/60Gi/DjG7xMTYndkHcOKPSb om4DnpePldKefQ/hcLC+2HMuIuiZG57nwBrFl9Ho4WWGGEwG2RNpA/wAQf13gMdP uTZSvan/Ir+CSG/3VJcfa1d0rJhhsw== =v57d -----END PGP SIGNATURE----- --nextPart1876656.tdWV9SEqCh-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v8 1/1] New options engine @ 2024-06-16 13:27 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2024-06-16 13:27 UTC (permalink / raw) 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 | 1366 ++++++++++ src/backend/access/common/reloptions.c | 2234 ++++------------- 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 | 19 +- 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/bin/pg_basebackup/streamutil.c | 2 +- src/include/access/amapi.h | 9 +- src/include/access/brin.h | 2 + src/include/access/brin_internal.h | 2 + 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 | 205 +- 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 + 48 files changed, 2609 insertions(+), 2333 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 fba3ba7771..8e6741bc74 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" @@ -206,7 +207,8 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *bloptions(Datum reloptions, bool validate); +extern void *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 6836129c90..5a7074f431 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 "catalog/index.h" #include "commands/vacuum.h" @@ -34,52 +34,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. */ @@ -137,7 +97,7 @@ blhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; - 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) +void * +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 755293456f..e39c8cd49a 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 249d82d3a0..26740d3ef7 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -197,7 +197,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 630b304338..e6df6dd53a 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -71,7 +71,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 517f20bc37..71581fba34 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 e3c1539a1e..31b7d940e7 100644 --- a/doc/src/sgml/indexam.sgml +++ b/doc/src/sgml/indexam.sgml @@ -480,27 +480,12 @@ amcostestimate (PlannerInfo *root, <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 d0f3848c95..01e332c033 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" @@ -279,7 +278,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -294,6 +292,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1329,23 +1328,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. @@ -3000,3 +2982,29 @@ brin_fill_empty_ranges(BrinBuildState *state, blkno += state->bs_pagesPerRange; } } + +static options_spec_set *brin_relopt_specset = NULL; + +void * +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 e78de31265..014d1b1562 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 14090c728f..0cfd469910 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 0000000000..8cc790365b --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1366 @@ +/*------------------------------------------------------------------------- + * + * 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-2024, 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, char **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 d6eb5d8559..7ec05df7b9 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-2024, 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,389 +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_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}, @@ -487,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}, @@ -505,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 @@ -733,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); } /* @@ -749,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 @@ -865,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); } /* @@ -919,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); } /* @@ -969,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); } /* @@ -1120,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, - char *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); } /* @@ -1377,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, @@ -1396,544 +251,326 @@ 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); + + 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_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; } /* @@ -1944,158 +581,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; } /* @@ -2105,33 +749,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 5747ae6a4c..3a67719ab9 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" /* @@ -69,7 +70,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; - amroutine->amoptions = ginoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = ginvalidate; @@ -84,6 +84,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gingetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -598,21 +599,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 * @@ -699,3 +685,29 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build) END_CRIT_SECTION(); } + +static options_spec_set *gin_relopt_specset = NULL; + +void * +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 ed4ffa63a7..b5a1521a83 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -91,7 +91,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -106,6 +105,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 78e98d68b1..865da66455 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" @@ -907,20 +907,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. * @@ -1055,3 +1041,39 @@ gistGetFakeLSN(Relation rel) return GetFakeLSNForUnloggedRel(); } } + +/* 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; + +void * +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 01d06b7c32..aae9541762 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -89,7 +89,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -104,6 +103,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index 20028f5cd1..bb02c08a3d 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; + +void * +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 686a3206f7..200189a4d0 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/xloginsert.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -133,7 +134,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -148,6 +148,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = btestimateparallelscan; amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1445,3 +1446,35 @@ btcanreturn(Relation index, int attno) { return true; } + +static options_spec_set *bt_relopt_specset = NULL; + +void * +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 05f0d23dc0..65658a5023 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" #include "access/relscan.h" #include "commands/progress.h" #include "lib/qunique.h" @@ -4557,23 +4557,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 3f793125f7..86a48d2eff 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" @@ -76,7 +76,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -91,6 +90,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -733,22 +733,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). @@ -1348,3 +1332,24 @@ spgproperty(Oid index_oid, int attno, return true; } + +static options_spec_set *spgist_relopt_specset = NULL; + +void * +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 62050f4dc5..936f640fdf 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; static char *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 cf61bbac1f..556cad13ed 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 309389e20d..649aa915b6 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -19,6 +19,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" @@ -559,7 +560,7 @@ DefineIndex(Oid tableId, IndexAmRoutine *amRoutine; bool amcanorder; bool amissummarizing; - amoptions_function amoptions; + amreloptspecset_function amreloptspecsetfn; bool partitioned; bool safe_index; Datum reloptions; @@ -870,7 +871,7 @@ DefineIndex(Oid tableId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; amissummarizing = amRoutine->amsummarizing; pfree(amRoutine); @@ -885,10 +886,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. @@ -2195,8 +2204,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 7b6c69b7a5..e145bf7957 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -20,6 +20,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" @@ -699,7 +700,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -842,19 +842,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 */ + char *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 */ + char *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) @@ -4366,7 +4383,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); @@ -4408,7 +4425,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. @@ -4629,9 +4646,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; @@ -8720,12 +8742,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)); @@ -14857,12 +14880,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]; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + char *heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -14882,38 +14906,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), @@ -14923,11 +14965,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; @@ -15002,20 +15048,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 113b480731..01aace1f1a 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 f4f35728b4..307195a148 100644 --- a/src/backend/foreign/foreign.c +++ b/src/backend/foreign/foreign.c @@ -79,7 +79,7 @@ GetForeignDataWrapperExtended(Oid fdwid, bits16 flags) if (isnull) fdw->options = NIL; else - fdw->options = untransformRelOptions(datum); + fdw->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -166,7 +166,7 @@ GetForeignServerExtended(Oid serverid, bits16 flags) if (isnull) server->options = NIL; else - server->options = untransformRelOptions(datum); + server->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -238,7 +238,7 @@ GetUserMapping(Oid userid, Oid serverid) if (isnull) um->options = NIL; else - um->options = untransformRelOptions(datum); + um->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -275,7 +275,7 @@ GetForeignTable(Oid relid) if (isnull) ft->options = NIL; else - ft->options = untransformRelOptions(datum); + ft->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -308,7 +308,7 @@ GetForeignColumnOptions(Oid relid, AttrNumber attnum) if (isnull) options = NIL; else - options = untransformRelOptions(datum); + options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -516,7 +516,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -614,7 +614,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 d5c2b2ff0b..d0df5822bd 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1726,7 +1726,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; @@ -1790,7 +1790,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 fa66b8017e..bafa48199b 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1156,6 +1156,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1179,15 +1180,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); @@ -1296,9 +1298,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 af978ccd4b..b295191d12 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" @@ -149,7 +150,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 cc9b0c6524..761811d940 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -463,7 +463,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -478,11 +478,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; @@ -493,7 +493,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 ec63cdc8e5..ea9552ca67 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/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index feee451d59..a611f9da2c 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -832,7 +832,7 @@ AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); - if (option_value != NULL) + if (option_value !=NULL) { size_t length = strlen(option_value); char *escaped_value = palloc(1 + 2 * length); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index f25c9d58a7..0ef48df437 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -140,10 +140,6 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, double *indexCorrelation, double *indexPages); -/* parse index reloptions */ -typedef bytea *(*amoptions_function) (Datum reloptions, - bool validate); - /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, @@ -190,6 +186,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan); /* restore marked scan position */ typedef void (*amrestrpos_function) (IndexScanDesc scan); +/* get catalog of reloptions definitions */ +typedef void *(*amreloptspecset_function) (); + /* * Callback function signatures - for parallel index scans. */ @@ -272,7 +271,6 @@ typedef struct IndexAmRoutine amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; - amoptions_function amoptions; amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; @@ -284,6 +282,7 @@ typedef struct IndexAmRoutine amendscan_function amendscan; ammarkpos_function ammarkpos; /* can be NULL */ amrestrpos_function amrestrpos; /* can be NULL */ + amreloptspecset_function amreloptspecset; /* can be NULL */ /* interface functions to support parallel index scans */ amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ diff --git a/src/include/access/brin.h b/src/include/access/brin.h index 1c786281dd..a552003224 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 a5a9772621..fd99dfae80 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -14,6 +14,7 @@ #include "access/amapi.h" #include "storage/bufpage.h" #include "utils/typcache.h" +#include "access/options.h" /* @@ -109,6 +110,7 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bytea *brinoptions(Datum reloptions, bool validate); +extern void *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 3013a44bae..5ba3a7578b 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 void *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 7b8749c8db..4579f1b5ff 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 void *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 9c7d81525b..6fa680afe3 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, @@ -474,6 +473,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 void *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 7493043348..0ec72c7dd7 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1298,7 +1298,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 void *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 0000000000..c87dc9e8d9 --- /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-2024, 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, + char **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 81829b8270..f80e1e840c 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-2024, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -22,178 +17,40 @@ #include "access/amapi.h" #include "access/htup.h" #include "access/tupdesc.h" +#include "access/options.h" #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 +67,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 +76,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, char *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 d6a4953120..7b113d7a76 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 2e9c757b30..a4d809cc29 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -529,6 +529,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 void *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 85cbad3d0c..1c7bb7f925 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 18185d0206..a8744fc415 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,7 +14,7 @@ #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" @@ -23,11 +23,7 @@ PG_MODULE_MAGIC; -/* parse table for fillRelOptions */ -relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -relopt_kind di_relopt_kind; +void _PG_init(void); typedef enum DummyAmEnum { @@ -47,7 +43,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -61,77 +57,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; +void *digetreloptspecset(void); + +void * +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. */ @@ -216,19 +217,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. */ @@ -308,7 +296,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = divacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -322,12 +309,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 8cbacca2c9..8319cd0694 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 9de19b4e3f..67f0a828f9 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 24fbe0b478..9b404295c0 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 --nextPart1901634.1xdlsreqCQ-- --nextPart2852682.AiC22s8V5E Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmZu8ZkACgkQPMbfuIHA GpgbiAf+Kt2ZEBfNF9ZNM2QVuoyqpyGmaWaoVrsLaPUBx0cgNbI2j8PzqdWyD5em PnTH2sqCwvO/WIJr/sM9ZcuPcg1XdQ3E29vLrjKtNCEjqTmt4ECc7hR0vbQ7FYZD fvi+52JS57kg656ExN0AjRzMLUjZqRCMhLCpPXF5qpY+xIbYkjs/j96QQliTKasW LP6Z2xApug/TPweNJNfTDkxIwHb+fpSUGcMFpdmMGaXNVa0tQks5EOm/ijrsldlT f1Ek9XfYftFICDYUcTX2bMenT32ki0kW1rPJw0jXFpw1WqBF4KgAkfgEQrugSlEm Jdr/DFe4KPMNuW52XD6Qe9bYRoQhhw== =mSDn -----END PGP SIGNATURE----- --nextPart2852682.AiC22s8V5E-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v8 1/1] New options engine @ 2024-06-16 13:27 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2024-06-16 13:27 UTC (permalink / raw) 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 | 1366 ++++++++++ src/backend/access/common/reloptions.c | 2234 ++++------------- 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 | 19 +- 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/bin/pg_basebackup/streamutil.c | 2 +- src/include/access/amapi.h | 9 +- src/include/access/brin.h | 2 + src/include/access/brin_internal.h | 2 + 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 | 205 +- 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 + 48 files changed, 2609 insertions(+), 2333 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 fba3ba7771..8e6741bc74 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" @@ -206,7 +207,8 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *bloptions(Datum reloptions, bool validate); +extern void *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 6836129c90..5a7074f431 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 "catalog/index.h" #include "commands/vacuum.h" @@ -34,52 +34,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. */ @@ -137,7 +97,7 @@ blhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; - 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) +void * +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 755293456f..e39c8cd49a 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 249d82d3a0..26740d3ef7 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -197,7 +197,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 630b304338..e6df6dd53a 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -71,7 +71,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 517f20bc37..71581fba34 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 e3c1539a1e..31b7d940e7 100644 --- a/doc/src/sgml/indexam.sgml +++ b/doc/src/sgml/indexam.sgml @@ -480,27 +480,12 @@ amcostestimate (PlannerInfo *root, <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 d0f3848c95..01e332c033 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" @@ -279,7 +278,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -294,6 +292,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1329,23 +1328,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. @@ -3000,3 +2982,29 @@ brin_fill_empty_ranges(BrinBuildState *state, blkno += state->bs_pagesPerRange; } } + +static options_spec_set *brin_relopt_specset = NULL; + +void * +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 e78de31265..014d1b1562 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 14090c728f..0cfd469910 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 0000000000..8cc790365b --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1366 @@ +/*------------------------------------------------------------------------- + * + * 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-2024, 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, char **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 d6eb5d8559..7ec05df7b9 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-2024, 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,389 +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_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}, @@ -487,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}, @@ -505,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 @@ -733,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); } /* @@ -749,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 @@ -865,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); } /* @@ -919,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); } /* @@ -969,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); } /* @@ -1120,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, - char *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); } /* @@ -1377,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, @@ -1396,544 +251,326 @@ 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); + + 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_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; } /* @@ -1944,158 +581,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; } /* @@ -2105,33 +749,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 5747ae6a4c..3a67719ab9 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" /* @@ -69,7 +70,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; - amroutine->amoptions = ginoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = ginvalidate; @@ -84,6 +84,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gingetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -598,21 +599,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 * @@ -699,3 +685,29 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build) END_CRIT_SECTION(); } + +static options_spec_set *gin_relopt_specset = NULL; + +void * +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 ed4ffa63a7..b5a1521a83 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -91,7 +91,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -106,6 +105,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 78e98d68b1..865da66455 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" @@ -907,20 +907,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. * @@ -1055,3 +1041,39 @@ gistGetFakeLSN(Relation rel) return GetFakeLSNForUnloggedRel(); } } + +/* 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; + +void * +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 01d06b7c32..aae9541762 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -89,7 +89,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -104,6 +103,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index 20028f5cd1..bb02c08a3d 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; + +void * +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 686a3206f7..200189a4d0 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/xloginsert.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -133,7 +134,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -148,6 +148,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = btestimateparallelscan; amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1445,3 +1446,35 @@ btcanreturn(Relation index, int attno) { return true; } + +static options_spec_set *bt_relopt_specset = NULL; + +void * +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 05f0d23dc0..65658a5023 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" #include "access/relscan.h" #include "commands/progress.h" #include "lib/qunique.h" @@ -4557,23 +4557,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 3f793125f7..86a48d2eff 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" @@ -76,7 +76,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -91,6 +90,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -733,22 +733,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). @@ -1348,3 +1332,24 @@ spgproperty(Oid index_oid, int attno, return true; } + +static options_spec_set *spgist_relopt_specset = NULL; + +void * +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 62050f4dc5..936f640fdf 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; static char *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 cf61bbac1f..556cad13ed 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 309389e20d..649aa915b6 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -19,6 +19,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" @@ -559,7 +560,7 @@ DefineIndex(Oid tableId, IndexAmRoutine *amRoutine; bool amcanorder; bool amissummarizing; - amoptions_function amoptions; + amreloptspecset_function amreloptspecsetfn; bool partitioned; bool safe_index; Datum reloptions; @@ -870,7 +871,7 @@ DefineIndex(Oid tableId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; amissummarizing = amRoutine->amsummarizing; pfree(amRoutine); @@ -885,10 +886,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. @@ -2195,8 +2204,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 7b6c69b7a5..e145bf7957 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -20,6 +20,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" @@ -699,7 +700,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -842,19 +842,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 */ + char *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 */ + char *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) @@ -4366,7 +4383,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); @@ -4408,7 +4425,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. @@ -4629,9 +4646,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; @@ -8720,12 +8742,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)); @@ -14857,12 +14880,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]; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + char *heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -14882,38 +14906,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), @@ -14923,11 +14965,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; @@ -15002,20 +15048,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 113b480731..01aace1f1a 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 f4f35728b4..307195a148 100644 --- a/src/backend/foreign/foreign.c +++ b/src/backend/foreign/foreign.c @@ -79,7 +79,7 @@ GetForeignDataWrapperExtended(Oid fdwid, bits16 flags) if (isnull) fdw->options = NIL; else - fdw->options = untransformRelOptions(datum); + fdw->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -166,7 +166,7 @@ GetForeignServerExtended(Oid serverid, bits16 flags) if (isnull) server->options = NIL; else - server->options = untransformRelOptions(datum); + server->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -238,7 +238,7 @@ GetUserMapping(Oid userid, Oid serverid) if (isnull) um->options = NIL; else - um->options = untransformRelOptions(datum); + um->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -275,7 +275,7 @@ GetForeignTable(Oid relid) if (isnull) ft->options = NIL; else - ft->options = untransformRelOptions(datum); + ft->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -308,7 +308,7 @@ GetForeignColumnOptions(Oid relid, AttrNumber attnum) if (isnull) options = NIL; else - options = untransformRelOptions(datum); + options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -516,7 +516,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -614,7 +614,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 d5c2b2ff0b..d0df5822bd 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1726,7 +1726,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; @@ -1790,7 +1790,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 fa66b8017e..bafa48199b 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1156,6 +1156,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1179,15 +1180,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); @@ -1296,9 +1298,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 af978ccd4b..b295191d12 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" @@ -149,7 +150,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 cc9b0c6524..761811d940 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -463,7 +463,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -478,11 +478,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; @@ -493,7 +493,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 ec63cdc8e5..ea9552ca67 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/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index feee451d59..a611f9da2c 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -832,7 +832,7 @@ AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); - if (option_value != NULL) + if (option_value !=NULL) { size_t length = strlen(option_value); char *escaped_value = palloc(1 + 2 * length); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index f25c9d58a7..0ef48df437 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -140,10 +140,6 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, double *indexCorrelation, double *indexPages); -/* parse index reloptions */ -typedef bytea *(*amoptions_function) (Datum reloptions, - bool validate); - /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, @@ -190,6 +186,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan); /* restore marked scan position */ typedef void (*amrestrpos_function) (IndexScanDesc scan); +/* get catalog of reloptions definitions */ +typedef void *(*amreloptspecset_function) (); + /* * Callback function signatures - for parallel index scans. */ @@ -272,7 +271,6 @@ typedef struct IndexAmRoutine amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; - amoptions_function amoptions; amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; @@ -284,6 +282,7 @@ typedef struct IndexAmRoutine amendscan_function amendscan; ammarkpos_function ammarkpos; /* can be NULL */ amrestrpos_function amrestrpos; /* can be NULL */ + amreloptspecset_function amreloptspecset; /* can be NULL */ /* interface functions to support parallel index scans */ amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ diff --git a/src/include/access/brin.h b/src/include/access/brin.h index 1c786281dd..a552003224 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 a5a9772621..fd99dfae80 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -14,6 +14,7 @@ #include "access/amapi.h" #include "storage/bufpage.h" #include "utils/typcache.h" +#include "access/options.h" /* @@ -109,6 +110,7 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bytea *brinoptions(Datum reloptions, bool validate); +extern void *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 3013a44bae..5ba3a7578b 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 void *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 7b8749c8db..4579f1b5ff 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 void *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 9c7d81525b..6fa680afe3 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, @@ -474,6 +473,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 void *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 7493043348..0ec72c7dd7 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1298,7 +1298,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 void *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 0000000000..c87dc9e8d9 --- /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-2024, 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, + char **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 81829b8270..f80e1e840c 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-2024, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -22,178 +17,40 @@ #include "access/amapi.h" #include "access/htup.h" #include "access/tupdesc.h" +#include "access/options.h" #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 +67,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 +76,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, char *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 d6a4953120..7b113d7a76 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 2e9c757b30..a4d809cc29 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -529,6 +529,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 void *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 85cbad3d0c..1c7bb7f925 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 18185d0206..a8744fc415 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,7 +14,7 @@ #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" @@ -23,11 +23,7 @@ PG_MODULE_MAGIC; -/* parse table for fillRelOptions */ -relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -relopt_kind di_relopt_kind; +void _PG_init(void); typedef enum DummyAmEnum { @@ -47,7 +43,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -61,77 +57,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; +void *digetreloptspecset(void); + +void * +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. */ @@ -216,19 +217,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. */ @@ -308,7 +296,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = divacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -322,12 +309,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 8cbacca2c9..8319cd0694 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 9de19b4e3f..67f0a828f9 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 24fbe0b478..9b404295c0 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 --nextPart1901634.1xdlsreqCQ-- --nextPart2852682.AiC22s8V5E Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmZu8ZkACgkQPMbfuIHA GpgbiAf+Kt2ZEBfNF9ZNM2QVuoyqpyGmaWaoVrsLaPUBx0cgNbI2j8PzqdWyD5em PnTH2sqCwvO/WIJr/sM9ZcuPcg1XdQ3E29vLrjKtNCEjqTmt4ECc7hR0vbQ7FYZD fvi+52JS57kg656ExN0AjRzMLUjZqRCMhLCpPXF5qpY+xIbYkjs/j96QQliTKasW LP6Z2xApug/TPweNJNfTDkxIwHb+fpSUGcMFpdmMGaXNVa0tQks5EOm/ijrsldlT f1Ek9XfYftFICDYUcTX2bMenT32ki0kW1rPJw0jXFpw1WqBF4KgAkfgEQrugSlEm Jdr/DFe4KPMNuW52XD6Qe9bYRoQhhw== =mSDn -----END PGP SIGNATURE----- --nextPart2852682.AiC22s8V5E-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v9 1/1] New options engine @ 2024-06-22 18:09 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2024-06-22 18:09 UTC (permalink / raw) 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 | 1366 ++++++++++ src/backend/access/common/reloptions.c | 2234 ++++------------- 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 | 19 +- 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/bin/pg_basebackup/streamutil.c | 2 +- src/include/access/amapi.h | 8 +- 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 + 48 files changed, 2607 insertions(+), 2332 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 fba3ba7771..c35bf13981 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" @@ -206,7 +207,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 6836129c90..670c8b67d8 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 "catalog/index.h" #include "commands/vacuum.h" @@ -34,52 +34,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. */ @@ -137,7 +97,7 @@ blhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; - 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 755293456f..e39c8cd49a 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 249d82d3a0..26740d3ef7 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -197,7 +197,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 630b304338..e6df6dd53a 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -71,7 +71,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 517f20bc37..71581fba34 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 e3c1539a1e..31b7d940e7 100644 --- a/doc/src/sgml/indexam.sgml +++ b/doc/src/sgml/indexam.sgml @@ -480,27 +480,12 @@ amcostestimate (PlannerInfo *root, <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 d0f3848c95..5eb7e04458 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" @@ -279,7 +278,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -294,6 +292,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1329,23 +1328,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. @@ -3000,3 +2982,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 e78de31265..014d1b1562 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 14090c728f..0cfd469910 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 0000000000..8cc790365b --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1366 @@ +/*------------------------------------------------------------------------- + * + * 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-2024, 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, char **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 d6eb5d8559..7ec05df7b9 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-2024, 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,389 +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_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}, @@ -487,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}, @@ -505,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 @@ -733,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); } /* @@ -749,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 @@ -865,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); } /* @@ -919,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); } /* @@ -969,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); } /* @@ -1120,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, - char *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); } /* @@ -1377,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, @@ -1396,544 +251,326 @@ 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); + + 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_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; } /* @@ -1944,158 +581,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; } /* @@ -2105,33 +749,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 5747ae6a4c..6b637a81bf 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" /* @@ -69,7 +70,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; - amroutine->amoptions = ginoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = ginvalidate; @@ -84,6 +84,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gingetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -598,21 +599,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 * @@ -699,3 +685,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 ed4ffa63a7..b5a1521a83 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -91,7 +91,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -106,6 +105,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 78e98d68b1..ed43b1bbda 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" @@ -907,20 +907,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. * @@ -1055,3 +1041,39 @@ gistGetFakeLSN(Relation rel) return GetFakeLSNForUnloggedRel(); } } + +/* 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 01d06b7c32..aae9541762 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -89,7 +89,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -104,6 +103,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index 20028f5cd1..0d1f7208d4 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 686a3206f7..cbcd5b8976 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/xloginsert.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -133,7 +134,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -148,6 +148,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = btestimateparallelscan; amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1445,3 +1446,35 @@ btcanreturn(Relation index, int attno) { return true; } + +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 05f0d23dc0..65658a5023 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" #include "access/relscan.h" #include "commands/progress.h" #include "lib/qunique.h" @@ -4557,23 +4557,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 3f793125f7..7ab983b036 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" @@ -76,7 +76,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -91,6 +90,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -733,22 +733,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). @@ -1348,3 +1332,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 62050f4dc5..936f640fdf 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; static char *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 cf61bbac1f..556cad13ed 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 309389e20d..649aa915b6 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -19,6 +19,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" @@ -559,7 +560,7 @@ DefineIndex(Oid tableId, IndexAmRoutine *amRoutine; bool amcanorder; bool amissummarizing; - amoptions_function amoptions; + amreloptspecset_function amreloptspecsetfn; bool partitioned; bool safe_index; Datum reloptions; @@ -870,7 +871,7 @@ DefineIndex(Oid tableId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; amissummarizing = amRoutine->amsummarizing; pfree(amRoutine); @@ -885,10 +886,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. @@ -2195,8 +2204,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 7b6c69b7a5..e145bf7957 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -20,6 +20,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" @@ -699,7 +700,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -842,19 +842,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 */ + char *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 */ + char *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) @@ -4366,7 +4383,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); @@ -4408,7 +4425,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. @@ -4629,9 +4646,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; @@ -8720,12 +8742,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)); @@ -14857,12 +14880,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]; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + char *heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -14882,38 +14906,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), @@ -14923,11 +14965,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; @@ -15002,20 +15048,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 113b480731..01aace1f1a 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 f4f35728b4..307195a148 100644 --- a/src/backend/foreign/foreign.c +++ b/src/backend/foreign/foreign.c @@ -79,7 +79,7 @@ GetForeignDataWrapperExtended(Oid fdwid, bits16 flags) if (isnull) fdw->options = NIL; else - fdw->options = untransformRelOptions(datum); + fdw->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -166,7 +166,7 @@ GetForeignServerExtended(Oid serverid, bits16 flags) if (isnull) server->options = NIL; else - server->options = untransformRelOptions(datum); + server->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -238,7 +238,7 @@ GetUserMapping(Oid userid, Oid serverid) if (isnull) um->options = NIL; else - um->options = untransformRelOptions(datum); + um->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -275,7 +275,7 @@ GetForeignTable(Oid relid) if (isnull) ft->options = NIL; else - ft->options = untransformRelOptions(datum); + ft->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -308,7 +308,7 @@ GetForeignColumnOptions(Oid relid, AttrNumber attnum) if (isnull) options = NIL; else - options = untransformRelOptions(datum); + options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -516,7 +516,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -614,7 +614,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 d5c2b2ff0b..d0df5822bd 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1726,7 +1726,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; @@ -1790,7 +1790,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 fa66b8017e..bafa48199b 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1156,6 +1156,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1179,15 +1180,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); @@ -1296,9 +1298,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 af978ccd4b..b295191d12 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" @@ -149,7 +150,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 cc9b0c6524..761811d940 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -463,7 +463,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -478,11 +478,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; @@ -493,7 +493,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 ec63cdc8e5..ea9552ca67 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/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index feee451d59..a611f9da2c 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -832,7 +832,7 @@ AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); - if (option_value != NULL) + if (option_value !=NULL) { size_t length = strlen(option_value); char *escaped_value = palloc(1 + 2 * length); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index f25c9d58a7..97ecede1d7 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -13,6 +13,7 @@ #define AMAPI_H #include "access/genam.h" +#include "access/options.h" /* * We don't wish to include planner header files here, since most of an index @@ -140,9 +141,8 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, double *indexCorrelation, double *indexPages); -/* 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, @@ -272,7 +272,7 @@ typedef struct IndexAmRoutine amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; - 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 1c786281dd..a552003224 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 a5a9772621..0ea5d1f7cc 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 3013a44bae..2532b29ae6 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 7b8749c8db..63ca6e1ae3 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 9c7d81525b..39f88e889a 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, @@ -474,6 +473,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 7493043348..72080f5351 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1298,7 +1298,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 0000000000..c87dc9e8d9 --- /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-2024, 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, + char **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 81829b8270..3572345102 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-2024, 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, char *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 d6a4953120..7b113d7a76 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 2e9c757b30..7e26fb3e55 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -529,6 +529,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 85cbad3d0c..1c7bb7f925 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 18185d0206..8d4150cd86 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,7 +14,7 @@ #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" @@ -23,11 +23,7 @@ PG_MODULE_MAGIC; -/* parse table for fillRelOptions */ -relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -relopt_kind di_relopt_kind; +void _PG_init(void); typedef enum DummyAmEnum { @@ -47,7 +43,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -61,77 +57,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. */ @@ -216,19 +217,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. */ @@ -308,7 +296,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = divacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -322,12 +309,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 8cbacca2c9..8319cd0694 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 9de19b4e3f..67f0a828f9 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 24fbe0b478..9b404295c0 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 --nextPart2114462.KlZ2vcFHjT-- --nextPart2659572.k3LOHGUjKi Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmZ3FhQACgkQPMbfuIHA GpiBNgf+O+BDrJw2wZbycVHSerccxWLoZLVuls1k+im4OhfzX5E4Ix6E2gbkcNRv r5TVxt6nkPmimOAUwoSk++yPHHP6DfQAkZh5uMsUA5R57ECaErtIJPWlTyrhTRaE 6VQJaqCb29ZYbFszH7jLn1mr7bfEaL8xs9Nvy+HVEkGRP7gfQgYZnmzldZbunqsT EncHgWA1JyKaRemLDQyxB40UoiTjlhrdv8fRRbM9CVtXR/qoo0WssVzxMxJVKoDY ffPY1bphQqJW4U/BDx0UhSbWEA4xqRD/qA428TbEVMbfKx89gSiWctMlL/RgM53S tOc4OH1OXVdvZCRhI4/NtL0xEkFUCg== =MI+v -----END PGP SIGNATURE----- --nextPart2659572.k3LOHGUjKi-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v9 1/1] New options engine @ 2024-06-22 18:09 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2024-06-22 18:09 UTC (permalink / raw) 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 | 1366 ++++++++++ src/backend/access/common/reloptions.c | 2234 ++++------------- 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 | 19 +- 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/bin/pg_basebackup/streamutil.c | 2 +- src/include/access/amapi.h | 8 +- 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 + 48 files changed, 2607 insertions(+), 2332 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 fba3ba7771..c35bf13981 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" @@ -206,7 +207,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 6836129c90..670c8b67d8 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 "catalog/index.h" #include "commands/vacuum.h" @@ -34,52 +34,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. */ @@ -137,7 +97,7 @@ blhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; - 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 755293456f..e39c8cd49a 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 249d82d3a0..26740d3ef7 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -197,7 +197,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 630b304338..e6df6dd53a 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -71,7 +71,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 517f20bc37..71581fba34 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 e3c1539a1e..31b7d940e7 100644 --- a/doc/src/sgml/indexam.sgml +++ b/doc/src/sgml/indexam.sgml @@ -480,27 +480,12 @@ amcostestimate (PlannerInfo *root, <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 d0f3848c95..5eb7e04458 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" @@ -279,7 +278,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; @@ -294,6 +292,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = bringetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1329,23 +1328,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. @@ -3000,3 +2982,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 e78de31265..014d1b1562 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 14090c728f..0cfd469910 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 0000000000..8cc790365b --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1366 @@ +/*------------------------------------------------------------------------- + * + * 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-2024, 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, char **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 d6eb5d8559..7ec05df7b9 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-2024, 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,389 +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_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}, @@ -487,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}, @@ -505,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 @@ -733,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); } /* @@ -749,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 @@ -865,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); } /* @@ -919,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); } /* @@ -969,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); } /* @@ -1120,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, - char *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); } /* @@ -1377,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, @@ -1396,544 +251,326 @@ 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); + + 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_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; } /* @@ -1944,158 +581,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; } /* @@ -2105,33 +749,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 5747ae6a4c..6b637a81bf 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" /* @@ -69,7 +70,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; - amroutine->amoptions = ginoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = ginvalidate; @@ -84,6 +84,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gingetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -598,21 +599,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 * @@ -699,3 +685,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 ed4ffa63a7..b5a1521a83 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -91,7 +91,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; @@ -106,6 +105,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = gistgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 78e98d68b1..ed43b1bbda 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" @@ -907,20 +907,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. * @@ -1055,3 +1041,39 @@ gistGetFakeLSN(Relation rel) return GetFakeLSNForUnloggedRel(); } } + +/* 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 01d06b7c32..aae9541762 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -89,7 +89,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; @@ -104,6 +103,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = hashgetreloptspecset; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index 20028f5cd1..0d1f7208d4 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 686a3206f7..cbcd5b8976 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/xloginsert.h" +#include "access/options.h" #include "commands/progress.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -133,7 +134,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; @@ -148,6 +148,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = btestimateparallelscan; amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amreloptspecset = btgetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -1445,3 +1446,35 @@ btcanreturn(Relation index, int attno) { return true; } + +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 05f0d23dc0..65658a5023 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include <time.h> #include "access/nbtree.h" -#include "access/reloptions.h" +#include "storage/lock.h" #include "access/relscan.h" #include "commands/progress.h" #include "lib/qunique.h" @@ -4557,23 +4557,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 3f793125f7..7ab983b036 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" @@ -76,7 +76,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; - amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; @@ -91,6 +90,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amreloptspecset = spggetreloptspecset; PG_RETURN_POINTER(amroutine); } @@ -733,22 +733,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). @@ -1348,3 +1332,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 62050f4dc5..936f640fdf 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; static char *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 cf61bbac1f..556cad13ed 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 309389e20d..649aa915b6 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -19,6 +19,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" @@ -559,7 +560,7 @@ DefineIndex(Oid tableId, IndexAmRoutine *amRoutine; bool amcanorder; bool amissummarizing; - amoptions_function amoptions; + amreloptspecset_function amreloptspecsetfn; bool partitioned; bool safe_index; Datum reloptions; @@ -870,7 +871,7 @@ DefineIndex(Oid tableId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; amissummarizing = amRoutine->amsummarizing; pfree(amRoutine); @@ -885,10 +886,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. @@ -2195,8 +2204,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 7b6c69b7a5..e145bf7957 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -20,6 +20,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" @@ -699,7 +700,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ListCell *listptr; AttrNumber attnum; bool partitioned; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; @@ -842,19 +842,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 */ + char *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 */ + char *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) @@ -4366,7 +4383,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); @@ -4408,7 +4425,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. @@ -4629,9 +4646,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; @@ -8720,12 +8742,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)); @@ -14857,12 +14880,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]; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *optionsDefList; + options_spec_set *optionsSpecSet; + char *heap_namespaces[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -14882,38 +14906,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), @@ -14923,11 +14965,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; @@ -15002,20 +15048,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 113b480731..01aace1f1a 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 f4f35728b4..307195a148 100644 --- a/src/backend/foreign/foreign.c +++ b/src/backend/foreign/foreign.c @@ -79,7 +79,7 @@ GetForeignDataWrapperExtended(Oid fdwid, bits16 flags) if (isnull) fdw->options = NIL; else - fdw->options = untransformRelOptions(datum); + fdw->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -166,7 +166,7 @@ GetForeignServerExtended(Oid serverid, bits16 flags) if (isnull) server->options = NIL; else - server->options = untransformRelOptions(datum); + server->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -238,7 +238,7 @@ GetUserMapping(Oid userid, Oid serverid) if (isnull) um->options = NIL; else - um->options = untransformRelOptions(datum); + um->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -275,7 +275,7 @@ GetForeignTable(Oid relid) if (isnull) ft->options = NIL; else - ft->options = untransformRelOptions(datum); + ft->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -308,7 +308,7 @@ GetForeignColumnOptions(Oid relid, AttrNumber attnum) if (isnull) options = NIL; else - options = untransformRelOptions(datum); + options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -516,7 +516,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) List *options; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - options = untransformRelOptions(array); + options = optionsTextArrayToDefList(array); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* prepare the result set */ @@ -614,7 +614,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 d5c2b2ff0b..d0df5822bd 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1726,7 +1726,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; @@ -1790,7 +1790,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 fa66b8017e..bafa48199b 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1156,6 +1156,7 @@ ProcessUtilitySlow(ParseState *pstate, CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; @@ -1179,15 +1180,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); @@ -1296,9 +1298,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 af978ccd4b..b295191d12 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" @@ -149,7 +150,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 cc9b0c6524..761811d940 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -463,7 +463,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -478,11 +478,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; @@ -493,7 +493,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 ec63cdc8e5..ea9552ca67 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/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index feee451d59..a611f9da2c 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -832,7 +832,7 @@ AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); - if (option_value != NULL) + if (option_value !=NULL) { size_t length = strlen(option_value); char *escaped_value = palloc(1 + 2 * length); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index f25c9d58a7..97ecede1d7 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -13,6 +13,7 @@ #define AMAPI_H #include "access/genam.h" +#include "access/options.h" /* * We don't wish to include planner header files here, since most of an index @@ -140,9 +141,8 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, double *indexCorrelation, double *indexPages); -/* 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, @@ -272,7 +272,7 @@ typedef struct IndexAmRoutine amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; - 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 1c786281dd..a552003224 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 a5a9772621..0ea5d1f7cc 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 3013a44bae..2532b29ae6 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 7b8749c8db..63ca6e1ae3 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 9c7d81525b..39f88e889a 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, @@ -474,6 +473,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 7493043348..72080f5351 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1298,7 +1298,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 0000000000..c87dc9e8d9 --- /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-2024, 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, + char **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 81829b8270..3572345102 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-2024, 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, char *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 d6a4953120..7b113d7a76 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 2e9c757b30..7e26fb3e55 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -529,6 +529,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 85cbad3d0c..1c7bb7f925 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 18185d0206..8d4150cd86 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -14,7 +14,7 @@ #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" @@ -23,11 +23,7 @@ PG_MODULE_MAGIC; -/* parse table for fillRelOptions */ -relopt_parse_elt di_relopt_tab[6]; - -/* Kind of relation options for dummy index */ -relopt_kind di_relopt_kind; +void _PG_init(void); typedef enum DummyAmEnum { @@ -47,7 +43,7 @@ typedef struct DummyIndexOptions int option_string_null_offset; } DummyIndexOptions; -relopt_enum_elt_def dummyAmEnumValues[] = +static opt_enum_elt_def dummyAmEnumValues[] = { {"one", DUMMY_AM_ENUM_ONE}, {"two", DUMMY_AM_ENUM_TWO}, @@ -61,77 +57,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. */ @@ -216,19 +217,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. */ @@ -308,7 +296,6 @@ dihandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = divacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = dicostestimate; - amroutine->amoptions = dioptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = divalidate; @@ -322,12 +309,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 8cbacca2c9..8319cd0694 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 9de19b4e3f..67f0a828f9 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 24fbe0b478..9b404295c0 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 --nextPart2114462.KlZ2vcFHjT-- --nextPart2659572.k3LOHGUjKi Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmZ3FhQACgkQPMbfuIHA GpiBNgf+O+BDrJw2wZbycVHSerccxWLoZLVuls1k+im4OhfzX5E4Ix6E2gbkcNRv r5TVxt6nkPmimOAUwoSk++yPHHP6DfQAkZh5uMsUA5R57ECaErtIJPWlTyrhTRaE 6VQJaqCb29ZYbFszH7jLn1mr7bfEaL8xs9Nvy+HVEkGRP7gfQgYZnmzldZbunqsT EncHgWA1JyKaRemLDQyxB40UoiTjlhrdv8fRRbM9CVtXR/qoo0WssVzxMxJVKoDY ffPY1bphQqJW4U/BDx0UhSbWEA4xqRD/qA428TbEVMbfKx89gSiWctMlL/RgM53S tOc4OH1OXVdvZCRhI4/NtL0xEkFUCg== =MI+v -----END PGP SIGNATURE----- --nextPart2659572.k3LOHGUjKi-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v10 1/1] New options engine @ 2025-02-10 18:08 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2025-02-10 18:08 UTC (permalink / raw) 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 --nextPart3671724.R56niFO833-- --nextPart3799907.MHq7AAxBmi Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmeqRvwACgkQPMbfuIHA GpjeJAf+P3NxXEgUp4/PsPeicY5o3wsnixdLhMQVo/WUd9dOs0iLm6RCHu234h6x iJBIP6d0qhSBRuxq8w9gPOErphSOwMPqGgBevXsaNZGvbl7o/T/zEH/g2fe95JgD reDjx4aJId5HAa0hjyudFiTm17tSm3M6w/92bOaVhDwlwr40zqPV8JANcVWn2hqJ dm46QDiCPDcR1FJzk8+7R6gaNA8FTO/ixfRRKM8OpbhqlmU4Dz/FF5K9tvkFSaZr eILNprqMDcLTZey9UwUKU+jkg3jJFnpjmXUsIysuaOZsGIunXbhYFiKu0o8lmSoc DI4O7uDPUOAJsqJegGCgolBAofLGxg== =zD7+ -----END PGP SIGNATURE----- --nextPart3799907.MHq7AAxBmi-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v10 1/1] New options engine @ 2025-02-10 18:08 Nikolay Shaplov <dhyan@nataraj.su> 0 siblings, 0 replies; 22+ messages in thread From: Nikolay Shaplov @ 2025-02-10 18:08 UTC (permalink / raw) 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 --nextPart3671724.R56niFO833-- --nextPart3799907.MHq7AAxBmi Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmeqRvwACgkQPMbfuIHA GpjeJAf+P3NxXEgUp4/PsPeicY5o3wsnixdLhMQVo/WUd9dOs0iLm6RCHu234h6x iJBIP6d0qhSBRuxq8w9gPOErphSOwMPqGgBevXsaNZGvbl7o/T/zEH/g2fe95JgD reDjx4aJId5HAa0hjyudFiTm17tSm3M6w/92bOaVhDwlwr40zqPV8JANcVWn2hqJ dm46QDiCPDcR1FJzk8+7R6gaNA8FTO/ixfRRKM8OpbhqlmU4Dz/FF5K9tvkFSaZr eILNprqMDcLTZey9UwUKU+jkg3jJFnpjmXUsIysuaOZsGIunXbhYFiKu0o8lmSoc DI4O7uDPUOAJsqJegGCgolBAofLGxg== =zD7+ -----END PGP SIGNATURE----- --nextPart3799907.MHq7AAxBmi-- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH 3/5] Add CONCURRENTLY option to REPACK command. @ 2026-03-11 14:16 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 22+ messages in thread From: Antonin Houska @ 2026-03-11 14:16 UTC (permalink / raw) The REPACK command copies the relation data into a new file, creates new indexes and eventually swaps the files. To make sure that the old file does not change during the copying, the relation is locked in an exclusive mode, which prevents applications from both reading and writing. (To keep the data consistent, we'd only need to prevent the applications from writing, but even reading needs to be blocked before we can swap the files - otherwise some applications could continue using the old file. Currently, REPACK takes the simple approach and acquires the exclusive lock in the beginning. This patch introduces an alternative workflow, which only requires the exclusive lock when the relation (and index) files are being swapped. (Supposedly, the swapping should be pretty fast.) On the other hand, when we copy the data to the new file, we allow applications to read from the relation and even to write to it. First, we scan the relation using a "historic snapshot", and insert all the tuples satisfying this snapshot into the new relation. Second, logical decoding is used to capture the data changes done by applications during the copying (i.e. changes not yet committed from the perspective of the historic snapshot mentioned above), and those are applied to the new file before we acquire the exclusive lock that we need to swap the files. (Of course, more data changes can take place while we are waiting for the lock - these will be applied to the new file after we have acquired the lock and before we swap the files.) While the "concurrent data" changes are applied at specific stages (we cannot do that until the intial copy is finished and indexes are built), a background worker performs the decoding all the time. This way we minimize the amount of not-yet-decoded WAL, so that archiving / recycling of WAL segments is not delayed much. The decoded changes are written to files and passed to the backed performing REPACK. Since the logical decoding system, during its startup, waits until all the transactions which already have XID assigned have finished, there is a risk of deadlock if a transaction that already changed anything in the database tries to acquire a conflicting lock on the table REPACK CONCURRENTLY is working on. As an example, consider transaction running CREATE INDEX command on the table that is being REPACKed CONCURRENTLY. On the other hand, DML commands (INSERT, UPDATE, DELETE) are not a problem as their lock does not conflict with REPACK CONCURRENTLY. The current approach is that we accept the risk. If we tried to avoid it, it'd be necessary to unlock the table before the logical decoding is setup and lock it again afterwards. Such temporary unlocking would imply re-checking if the table still meets all the requirements for REPACK CONCURRENTLY. The WAL records produced by running DML commands on the new relation are intentionally not fed to the logical decoding system. Doing so would introduce significant overhead, and - as the new relation is never available for logical replication - it would be useless. --- doc/src/sgml/monitoring.sgml | 37 +- doc/src/sgml/mvcc.sgml | 12 +- doc/src/sgml/ref/repack.sgml | 112 +- src/Makefile | 1 + src/backend/access/heap/heapam.c | 34 +- src/backend/access/heap/heapam_handler.c | 208 +- src/backend/access/heap/rewriteheap.c | 6 +- src/backend/catalog/system_views.sql | 19 +- src/backend/commands/cluster.c | 2295 ++++++++++++++++- src/backend/commands/matview.c | 1 + src/backend/commands/tablecmds.c | 1 + src/backend/commands/vacuum.c | 12 +- src/backend/libpq/pqmq.c | 5 + src/backend/meson.build | 1 + src/backend/postmaster/bgworker.c | 5 + src/backend/replication/logical/decode.c | 37 +- src/backend/replication/logical/logical.c | 6 +- src/backend/replication/logical/snapbuild.c | 11 +- .../replication/pgoutput_repack/Makefile | 32 + .../replication/pgoutput_repack/meson.build | 18 + .../pgoutput_repack/pgoutput_repack.c | 202 ++ src/backend/replication/walsender.c | 2 +- src/backend/storage/ipc/procsignal.c | 4 + .../storage/lmgr/generate-lwlocknames.pl | 2 +- src/backend/tcop/postgres.c | 4 + .../utils/activity/wait_event_names.txt | 1 + src/backend/utils/time/snapmgr.c | 3 +- src/bin/psql/tab-complete.in.c | 4 +- src/include/access/heapam.h | 5 +- src/include/access/heapam_xlog.h | 2 + src/include/access/tableam.h | 5 + src/include/commands/cluster.h | 55 +- src/include/commands/progress.h | 17 +- src/include/replication/snapbuild.h | 2 +- src/include/storage/lockdefs.h | 4 +- src/include/storage/procsignal.h | 1 + src/include/utils/snapmgr.h | 2 + src/test/modules/injection_points/Makefile | 2 + .../injection_points/expected/repack.out | 113 + .../expected/repack_toast.out | 64 + src/test/modules/injection_points/meson.build | 2 + .../injection_points/specs/repack.spec | 142 + .../injection_points/specs/repack_toast.spec | 105 + src/test/regress/expected/rules.out | 19 +- src/tools/pgindent/typedefs.list | 6 + 45 files changed, 3364 insertions(+), 257 deletions(-) create mode 100644 src/backend/replication/pgoutput_repack/Makefile create mode 100644 src/backend/replication/pgoutput_repack/meson.build create mode 100644 src/backend/replication/pgoutput_repack/pgoutput_repack.c create mode 100644 src/test/modules/injection_points/expected/repack.out create mode 100644 src/test/modules/injection_points/expected/repack_toast.out create mode 100644 src/test/modules/injection_points/specs/repack.spec create mode 100644 src/test/modules/injection_points/specs/repack_toast.spec diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index cc014564c97..f52b5dc10e8 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -6839,14 +6839,35 @@ FROM pg_stat_get_backend_idset() AS backendid; <row> <entry role="catalog_table_entry"><para role="column_definition"> - <structfield>heap_tuples_written</structfield> <type>bigint</type> + <structfield>heap_tuples_inserted</structfield> <type>bigint</type> </para> <para> - Number of heap tuples written. + Number of heap tuples inserted. This counter only advances when the phase is <literal>seq scanning heap</literal>, - <literal>index scanning heap</literal> - or <literal>writing new heap</literal>. + <literal>index scanning heap</literal>, + <literal>writing new heap</literal> + or <literal>catch-up</literal>. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>heap_tuples_updated</structfield> <type>bigint</type> + </para> + <para> + Number of heap tuples updated. + This counter only advances when the phase is <literal>catch-up</literal>. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>heap_tuples_deleted</structfield> <type>bigint</type> + </para> + <para> + Number of heap tuples deleted. + This counter only advances when the phase is <literal>catch-up</literal>. </para></entry> </row> @@ -6927,6 +6948,14 @@ FROM pg_stat_get_backend_idset() AS backendid; <command>REPACK</command> is currently writing the new heap. </entry> </row> + <row> + <entry><literal>catch-up</literal></entry> + <entry> + <command>REPACK CONCURRENTLY</command> is currently processing the DML + commands that other transactions executed during any of the preceding + phases. + </entry> + </row> <row> <entry><literal>swapping relation files</literal></entry> <entry> diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml index e775260936a..241caeb3593 100644 --- a/doc/src/sgml/mvcc.sgml +++ b/doc/src/sgml/mvcc.sgml @@ -1845,15 +1845,17 @@ SELECT pg_advisory_lock(q.id) FROM <title>Caveats</title> <para> - Some DDL commands, currently only <link linkend="sql-truncate"><command>TRUNCATE</command></link> and the - table-rewriting forms of <link linkend="sql-altertable"><command>ALTER TABLE</command></link>, are not + Some commands, currently only <link linkend="sql-truncate"><command>TRUNCATE</command></link>, the + table-rewriting forms of <link linkend="sql-altertable"><command>ALTER + TABLE</command></link> and <command>REPACK</command> with + the <literal>CONCURRENTLY</literal> option, are not MVCC-safe. This means that after the truncation or rewrite commits, the table will appear empty to concurrent transactions, if they are using a - snapshot taken before the DDL command committed. This will only be an + snapshot taken before the command committed. This will only be an issue for a transaction that did not access the table in question - before the DDL command started — any transaction that has done so + before the command started — any transaction that has done so would hold at least an <literal>ACCESS SHARE</literal> table lock, - which would block the DDL command until that transaction completes. + which would block the truncating or rewriting command until that transaction completes. So these commands will not cause any apparent inconsistency in the table contents for successive queries on the target table, but they could cause visible inconsistency between the contents of the target diff --git a/doc/src/sgml/ref/repack.sgml b/doc/src/sgml/ref/repack.sgml index 8ccf7c7a417..170eb84bf43 100644 --- a/doc/src/sgml/ref/repack.sgml +++ b/doc/src/sgml/ref/repack.sgml @@ -28,6 +28,7 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING VERBOSE [ <replaceable class="parameter">boolean</replaceable> ] ANALYZE [ <replaceable class="parameter">boolean</replaceable> ] + CONCURRENTLY [ <replaceable class="parameter">boolean</replaceable> ] <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase> @@ -54,7 +55,8 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING processes every table and materialized view in the current database that the current user has the <literal>MAINTAIN</literal> privilege on. This form of <command>REPACK</command> cannot be executed inside a transaction - block. + block. Also, this form is not allowed if + the <literal>CONCURRENTLY</literal> option is used. </para> <para> @@ -67,7 +69,8 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING When a table is being repacked, an <literal>ACCESS EXCLUSIVE</literal> lock is acquired on it. This prevents any other database operations (both reads and writes) from operating on the table until the <command>REPACK</command> - is finished. + is finished. If you want to keep the table accessible during the repacking, + consider using the <literal>CONCURRENTLY</literal> option. </para> <refsect2 id="sql-repack-notes-on-clustering" xreflabel="Notes on Clustering"> @@ -198,6 +201,111 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING </listitem> </varlistentry> + <varlistentry> + <term><literal>CONCURRENTLY</literal></term> + <listitem> + <para> + Allow other transactions to use the table while it is being repacked. + </para> + + <para> + Internally, <command>REPACK</command> copies the contents of the table + (ignoring dead tuples) into a new file, sorted by the specified index, + and also creates a new file for each index. Then it swaps the old and + new files for the table and all the indexes, and deletes the old + files. The <literal>ACCESS EXCLUSIVE</literal> lock is needed to make + sure that the old files do not change during the processing because the + changes would get lost due to the swap. + </para> + + <para> + With the <literal>CONCURRENTLY</literal> option, the <literal>ACCESS + EXCLUSIVE</literal> lock is only acquired to swap the table and index + files. The data changes that took place during the creation of the new + table and index files are captured using logical decoding + (<xref linkend="logicaldecoding"/>) and applied before + the <literal>ACCESS EXCLUSIVE</literal> lock is requested. Thus the lock + is typically held only for the time needed to swap the files, which + should be pretty short. However, the time might still be noticeable if + too many data changes have been done to the table while + <command>REPACK</command> was waiting for the lock: those changes must + be processed just before the files are swapped, while the + <literal>ACCESS EXCLUSIVE</literal> lock is being held. + </para> + + <para> + Note that <command>REPACK</command> with the + <literal>CONCURRENTLY</literal> option does not try to order the rows + inserted into the table after the repacking started. Also + note <command>REPACK</command> might fail to complete due to DDL + commands executed on the table by other transactions during the + repacking. + </para> + + <note> + <para> + In addition to the temporary space requirements explained in + <xref linkend="sql-repack-notes-on-resources"/>, + the <literal>CONCURRENTLY</literal> option can add to the usage of + temporary space a bit more. The reason is that other transactions can + perform DML operations which cannot be applied to the new file until + <command>REPACK</command> has copied all the existing tuples from the + old file. Thus the tuples inserted into the old file during the copying + are also stored separately in a temporary file, until they can be + processed. + </para> + </note> + + <para> + The <literal>CONCURRENTLY</literal> option cannot be used in the + following cases: + + <itemizedlist> + <listitem> + <para> + The table is <literal>UNLOGGED</literal>. + </para> + </listitem> + + <listitem> + <para> + The table is partitioned. + </para> + </listitem> + + <listitem> + <para> + The table is a system catalog or a <acronym>TOAST</acronym> table. + </para> + </listitem> + + <listitem> + <para> + <command>REPACK</command> is executed inside a transaction block. + </para> + </listitem> + + <listitem> + <para> + The <link linkend="guc-max-replication-slots"><varname>max_replication_slots</varname></link> + configuration parameter does not allow for creation of an additional + replication slot. + </para> + </listitem> + </itemizedlist> + </para> + + <warning> + <para> + <command>REPACK</command> with the <literal>CONCURRENTLY</literal> + option is not MVCC-safe, see <xref linkend="mvcc-caveats"/> for + details. + </para> + </warning> + + </listitem> + </varlistentry> + <varlistentry> <term><literal>VERBOSE</literal></term> <listitem> diff --git a/src/Makefile b/src/Makefile index 2f31a2f20a7..b18c9a14ffa 100644 --- a/src/Makefile +++ b/src/Makefile @@ -23,6 +23,7 @@ SUBDIRS = \ interfaces \ backend/replication/libpqwalreceiver \ backend/replication/pgoutput \ + backend/replication/pgoutput_repack \ fe_utils \ bin \ pl \ diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 8f1c11a9350..f25fac19a97 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -61,7 +61,8 @@ static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup, static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf, Buffer newbuf, HeapTuple oldtup, HeapTuple newtup, HeapTuple old_key_tuple, - bool all_visible_cleared, bool new_all_visible_cleared); + bool all_visible_cleared, bool new_all_visible_cleared, + bool walLogical); #ifdef USE_ASSERT_CHECKING static void check_lock_if_inplace_updateable_rel(Relation relation, const ItemPointerData *otid, @@ -2843,7 +2844,7 @@ xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask) TM_Result heap_delete(Relation relation, const ItemPointerData *tid, CommandId cid, Snapshot crosscheck, bool wait, - TM_FailureData *tmfd, bool changingPart) + TM_FailureData *tmfd, bool changingPart, bool walLogical) { TM_Result result; TransactionId xid = GetCurrentTransactionId(); @@ -3090,7 +3091,8 @@ l1: * Compute replica identity tuple before entering the critical section so * we don't PANIC upon a memory allocation failure. */ - old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, &old_key_copied); + old_key_tuple = walLogical ? + ExtractReplicaIdentity(relation, &tp, true, &old_key_copied) : NULL; /* * If this is the first possibly-multixact-able operation in the current @@ -3180,6 +3182,15 @@ l1: xlrec.flags |= XLH_DELETE_CONTAINS_OLD_KEY; } + /* + * Unlike UPDATE, DELETE is decoded even if there is no old key, so it + * does not help to clear both XLH_DELETE_CONTAINS_OLD_TUPLE and + * XLH_DELETE_CONTAINS_OLD_KEY. Thus we need an extra flag. TODO + * Consider not decoding tuples w/o the old tuple/key instead. + */ + if (!walLogical) + xlrec.flags |= XLH_DELETE_NO_LOGICAL; + XLogBeginInsert(); XLogRegisterData(&xlrec, SizeOfHeapDelete); @@ -3272,7 +3283,8 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid) result = heap_delete(relation, tid, GetCurrentCommandId(true), InvalidSnapshot, true /* wait for commit */ , - &tmfd, false /* changingPart */ ); + &tmfd, false, /* changingPart */ + true /* walLogical */ ); switch (result) { case TM_SelfModified: @@ -3313,7 +3325,7 @@ TM_Result heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode, - TU_UpdateIndexes *update_indexes) + TU_UpdateIndexes *update_indexes, bool walLogical) { TM_Result result; TransactionId xid = GetCurrentTransactionId(); @@ -4206,7 +4218,8 @@ l2: newbuf, &oldtup, heaptup, old_key_tuple, all_visible_cleared, - all_visible_cleared_new); + all_visible_cleared_new, + walLogical); if (newbuf != buffer) { PageSetLSN(BufferGetPage(newbuf), recptr); @@ -4564,7 +4577,8 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup result = heap_update(relation, otid, tup, GetCurrentCommandId(true), InvalidSnapshot, true /* wait for commit */ , - &tmfd, &lockmode, update_indexes); + &tmfd, &lockmode, update_indexes, + true /* walLogical */ ); switch (result) { case TM_SelfModified: @@ -8925,7 +8939,8 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf, Buffer newbuf, HeapTuple oldtup, HeapTuple newtup, HeapTuple old_key_tuple, - bool all_visible_cleared, bool new_all_visible_cleared) + bool all_visible_cleared, bool new_all_visible_cleared, + bool walLogical) { xl_heap_update xlrec; xl_heap_header xlhdr; @@ -8936,7 +8951,8 @@ log_heap_update(Relation reln, Buffer oldbuf, suffixlen = 0; XLogRecPtr recptr; Page page = BufferGetPage(newbuf); - bool need_tuple_data = RelationIsLogicallyLogged(reln); + bool need_tuple_data = RelationIsLogicallyLogged(reln) && + walLogical; bool init; int bufflags; diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 5137d2510ea..f4c62e5bc7a 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -309,7 +309,8 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid, * the storage itself is cleaning the dead tuples by itself, it is the * time to call the index tuple deletion also. */ - return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart); + return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart, + true); } @@ -328,7 +329,7 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot, tuple->t_tableOid = slot->tts_tableOid; result = heap_update(relation, otid, tuple, cid, crosscheck, wait, - tmfd, lockmode, update_indexes); + tmfd, lockmode, update_indexes, true); ItemPointerCopy(&tuple->t_self, &slot->tts_tid); /* @@ -685,13 +686,14 @@ static void heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, Relation OldIndex, bool use_sort, TransactionId OldestXmin, + Snapshot snapshot, TransactionId *xid_cutoff, MultiXactId *multi_cutoff, double *num_tuples, double *tups_vacuumed, double *tups_recently_dead) { - RewriteState rwstate; + RewriteState rwstate = NULL; IndexScanDesc indexScan; TableScanDesc tableScan; HeapScanDesc heapScan; @@ -705,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, bool *isnull; BufferHeapTupleTableSlot *hslot; BlockNumber prev_cblock = InvalidBlockNumber; + bool concurrent = snapshot != NULL; /* Remember if it's a system catalog */ is_system_catalog = IsSystemRelation(OldHeap); @@ -720,9 +723,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, values = palloc_array(Datum, natts); isnull = palloc_array(bool, natts); - /* Initialize the rewrite operation */ - rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, *xid_cutoff, - *multi_cutoff); + /* + * Initialize the rewrite operation. + */ + if (!concurrent) + rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, + *xid_cutoff, *multi_cutoff); /* Set up sorting if wanted */ @@ -737,6 +743,9 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, * Prepare to scan the OldHeap. To ensure we see recently-dead tuples * that still need to be copied, we scan with SnapshotAny and use * HeapTupleSatisfiesVacuum for the visibility test. + * + * In the CONCURRENTLY case, we do regular MVCC visibility tests, using + * the snapshot passed by the caller. */ if (OldIndex != NULL && !use_sort) { @@ -753,7 +762,9 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, tableScan = NULL; heapScan = NULL; - indexScan = index_beginscan(OldHeap, OldIndex, SnapshotAny, NULL, 0, 0); + indexScan = index_beginscan(OldHeap, OldIndex, + snapshot ? snapshot : SnapshotAny, + NULL, 0, 0); index_rescan(indexScan, NULL, 0, NULL, 0); } else @@ -762,7 +773,9 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, pgstat_progress_update_param(PROGRESS_REPACK_PHASE, PROGRESS_REPACK_PHASE_SEQ_SCAN_HEAP); - tableScan = table_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL); + tableScan = table_beginscan(OldHeap, + snapshot ? snapshot : SnapshotAny, + 0, (ScanKey) NULL); heapScan = (HeapScanDesc) tableScan; indexScan = NULL; @@ -838,83 +851,91 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, buf = hslot->buffer; /* - * To be able to guarantee that we can set the hint bit, acquire an - * exclusive lock on the old buffer. We need the hint bits, set in - * heapam_relation_copy_for_cluster() -> HeapTupleSatisfiesVacuum(), - * to be set, as otherwise reform_and_rewrite_tuple() -> - * rewrite_heap_tuple() will get confused. Specifically, - * rewrite_heap_tuple() checks for HEAP_XMAX_INVALID in the old tuple - * to determine whether to check the old-to-new mapping hash table. - * - * It'd be better if we somehow could avoid setting hint bits on the - * old page. One reason to use VACUUM FULL are very bloated tables - - * rewriting most of the old table during VACUUM FULL doesn't exactly - * help... + * Regarding CONCURRENTLY, see the comments on MVCC snapshot above. */ - LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); - - switch (HeapTupleSatisfiesVacuum(tuple, OldestXmin, buf)) + if (!concurrent) { - case HEAPTUPLE_DEAD: - /* Definitely dead */ - isdead = true; - break; - case HEAPTUPLE_RECENTLY_DEAD: - *tups_recently_dead += 1; - pg_fallthrough; - case HEAPTUPLE_LIVE: - /* Live or recently dead, must copy it */ - isdead = false; - break; - case HEAPTUPLE_INSERT_IN_PROGRESS: + /* + * To be able to guarantee that we can set the hint bit, acquire + * an exclusive lock on the old buffer. We need the hint bits, set + * in heapam_relation_copy_for_cluster() -> + * HeapTupleSatisfiesVacuum(), to be set, as otherwise + * reform_and_rewrite_tuple() -> rewrite_heap_tuple() will get + * confused. Specifically, rewrite_heap_tuple() checks for + * HEAP_XMAX_INVALID in the old tuple to determine whether to + * check the old-to-new mapping hash table. + * + * It'd be better if we somehow could avoid setting hint bits on + * the old page. One reason to use VACUUM FULL are very bloated + * tables - rewriting most of the old table during VACUUM FULL + * doesn't exactly help... + */ + LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); - /* - * Since we hold exclusive lock on the relation, normally the - * only way to see this is if it was inserted earlier in our - * own transaction. However, it can happen in system - * catalogs, since we tend to release write lock before commit - * there. Give a warning if neither case applies; but in any - * case we had better copy it. - */ - if (!is_system_catalog && - !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data))) - elog(WARNING, "concurrent insert in progress within table \"%s\"", - RelationGetRelationName(OldHeap)); - /* treat as live */ - isdead = false; - break; - case HEAPTUPLE_DELETE_IN_PROGRESS: + switch (HeapTupleSatisfiesVacuum(tuple, OldestXmin, buf)) + { + case HEAPTUPLE_DEAD: + /* Definitely dead */ + isdead = true; + break; + case HEAPTUPLE_RECENTLY_DEAD: + *tups_recently_dead += 1; + pg_fallthrough; + case HEAPTUPLE_LIVE: + /* Live or recently dead, must copy it */ + isdead = false; + break; + case HEAPTUPLE_INSERT_IN_PROGRESS: - /* - * Similar situation to INSERT_IN_PROGRESS case. - */ - if (!is_system_catalog && - !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple->t_data))) - elog(WARNING, "concurrent delete in progress within table \"%s\"", - RelationGetRelationName(OldHeap)); - /* treat as recently dead */ - *tups_recently_dead += 1; - isdead = false; - break; - default: - elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result"); - isdead = false; /* keep compiler quiet */ - break; - } + /* + * As long as we hold exclusive lock on the relation, + * normally the only way to see this is if it was inserted + * earlier in our own transaction. However, it can happen + * in system catalogs, since we tend to release write lock + * before commit there. Give a warning if neither case + * applies; but in any case we had better copy it. + */ + if (!is_system_catalog && + !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data))) + elog(WARNING, "concurrent insert in progress within table \"%s\"", + RelationGetRelationName(OldHeap)); + /* treat as live */ + isdead = false; + break; + case HEAPTUPLE_DELETE_IN_PROGRESS: - LockBuffer(buf, BUFFER_LOCK_UNLOCK); + /* + * Similar situation to INSERT_IN_PROGRESS case. + */ + if (!is_system_catalog && + !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple->t_data))) + elog(WARNING, "concurrent delete in progress within table \"%s\"", + RelationGetRelationName(OldHeap)); + /* treat as recently dead */ + *tups_recently_dead += 1; + isdead = false; + break; + default: + elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result"); + isdead = false; /* keep compiler quiet */ + break; + } - if (isdead) - { - *tups_vacuumed += 1; - /* heap rewrite module still needs to see it... */ - if (rewrite_heap_dead_tuple(rwstate, tuple)) + LockBuffer(buf, BUFFER_LOCK_UNLOCK); + + if (isdead) { - /* A previous recently-dead tuple is now known dead */ *tups_vacuumed += 1; - *tups_recently_dead -= 1; + /* heap rewrite module still needs to see it... */ + if (rewrite_heap_dead_tuple(rwstate, tuple)) + { + /* A previous recently-dead tuple is now known dead */ + *tups_vacuumed += 1; + *tups_recently_dead -= 1; + } + + continue; } - continue; } *num_tuples += 1; @@ -933,7 +954,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, { const int ct_index[] = { PROGRESS_REPACK_HEAP_TUPLES_SCANNED, - PROGRESS_REPACK_HEAP_TUPLES_WRITTEN + PROGRESS_REPACK_HEAP_TUPLES_INSERTED }; int64 ct_val[2]; @@ -991,7 +1012,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, values, isnull, rwstate); /* Report n_tuples */ - pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_WRITTEN, + pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, n_tuples); } @@ -999,7 +1020,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, } /* Write out any remaining tuples, and fsync if needed */ - end_heap_rewrite(rwstate); + if (rwstate) + end_heap_rewrite(rwstate); /* Clean up */ pfree(values); @@ -2392,6 +2414,10 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate, * SET WITHOUT OIDS. * * So, we must reconstruct the tuple from component Datums. + * + * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that + * case we still need to deform/form the tuple. TODO Shouldn't we rename the + * function, as might not do any rewrite? */ static void reform_and_rewrite_tuple(HeapTuple tuple, @@ -2414,8 +2440,28 @@ reform_and_rewrite_tuple(HeapTuple tuple, copiedTuple = heap_form_tuple(newTupDesc, values, isnull); - /* The heap rewrite module does the rest */ - rewrite_heap_tuple(rwstate, tuple, copiedTuple); + if (rwstate) + /* The heap rewrite module does the rest */ + rewrite_heap_tuple(rwstate, tuple, copiedTuple); + else + { + /* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * The following is like simple_heap_insert() except that we pass the + * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps + * the relation files, it drops this relation, so no logical + * replication subscription should need the data. + */ + heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true), + HEAP_INSERT_NO_LOGICAL, NULL); + } heap_freetuple(copiedTuple); } diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 6b19ac3030d..d706856e7a5 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -621,9 +621,9 @@ raw_heap_insert(RewriteState state, HeapTuple tup) int options = HEAP_INSERT_SKIP_FSM; /* - * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data - * for the TOAST table are not logically decoded. The main heap is - * WAL-logged as XLOG FPI records, which are not logically decoded. + * While rewriting the heap for REPACK, make sure data for the TOAST + * table are not logically decoded. The main heap is WAL-logged as + * XLOG FPI records, which are not logically decoded. */ options |= HEAP_INSERT_NO_LOGICAL; diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 339c016e510..d7a6e306db9 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -1326,16 +1326,19 @@ CREATE VIEW pg_stat_progress_repack AS WHEN 2 THEN 'index scanning heap' WHEN 3 THEN 'sorting tuples' WHEN 4 THEN 'writing new heap' - WHEN 5 THEN 'swapping relation files' - WHEN 6 THEN 'rebuilding index' - WHEN 7 THEN 'performing final cleanup' + WHEN 5 THEN 'catch-up' + WHEN 6 THEN 'swapping relation files' + WHEN 7 THEN 'rebuilding index' + WHEN 8 THEN 'performing final cleanup' END AS phase, CAST(S.param3 AS oid) AS repack_index_relid, S.param4 AS heap_tuples_scanned, - S.param5 AS heap_tuples_written, - S.param6 AS heap_blks_total, - S.param7 AS heap_blks_scanned, - S.param8 AS index_rebuild_count + S.param5 AS heap_tuples_inserted, + S.param6 AS heap_tuples_updated, + S.param7 AS heap_tuples_deleted, + S.param8 AS heap_blks_total, + S.param9 AS heap_blks_scanned, + S.param10 AS index_rebuild_count FROM pg_stat_get_progress_info('REPACK') AS S LEFT JOIN pg_database D ON S.datid = D.oid; @@ -1353,7 +1356,7 @@ CREATE VIEW pg_stat_progress_cluster AS phase, repack_index_relid AS cluster_index_relid, heap_tuples_scanned, - heap_tuples_written, + heap_tuples_inserted + heap_tuples_updated AS heap_tuples_written, heap_blks_total, heap_blks_scanned, index_rebuild_count diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 3bfaa663699..fe419ef808e 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -4,6 +4,22 @@ * REPACK a table; formerly known as CLUSTER. VACUUM FULL also uses * parts of this code. * + * There are two somewhat different ways to rewrite a table. In non- + * concurrent mode, it's easy: take AccessExclusiveLock, create a new + * transient relation, copy the tuples over to the relfilenode of the new + * relation, swap the relfilenodes, then drop the old relation. + * + * In concurrent mode, we lock the table with only ShareUpdateExclusiveLock, + * then do an initial copy as above. However, while the tuples are being + * copied, concurrent transactions could modify the table. To cope with those + * changes, we rely on logical decoding to obtain them from WAL. A bgworker + * consumes WAL while the initial copy is ongoing (to prevent excessive WAL + * from being reserved), and accumulates the changes in a file. Once the + * initial copy is complete, we read the changes from the file and re-apply + * them on the new heap. Then we upgrade our ShareUpdateExclusiveLock to + * AccessExclusiveLock and swap the relfilenodes. This way, the time we hold + * a strong lock on the table is much reduced, and the bloat is eliminated. + * * * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group * Portions Copyright (c) 1994-5, Regents of the University of California @@ -24,6 +40,11 @@ #include "access/toast_internals.h" #include "access/transam.h" #include "access/xact.h" +#include "access/xlog.h" +#include "access/xlog_internal.h" +#include "access/xloginsert.h" +#include "access/xlogutils.h" +#include "access/xlogwait.h" #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/heap.h" @@ -31,6 +52,7 @@ #include "catalog/namespace.h" #include "catalog/objectaccess.h" #include "catalog/pg_am.h" +#include "catalog/pg_control.h" #include "catalog/pg_inherits.h" #include "catalog/toasting.h" #include "commands/cluster.h" @@ -38,15 +60,26 @@ #include "commands/progress.h" #include "commands/tablecmds.h" #include "commands/vacuum.h" +#include "executor/executor.h" +#include "libpq/pqformat.h" +#include "libpq/pqmq.h" #include "miscadmin.h" #include "optimizer/optimizer.h" #include "pgstat.h" +#include "replication/decode.h" +#include "replication/logical.h" +#include "replication/snapbuild.h" #include "storage/bufmgr.h" +#include "storage/ipc.h" #include "storage/lmgr.h" #include "storage/predicate.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "tcop/tcopprot.h" #include "utils/acl.h" #include "utils/fmgroids.h" #include "utils/guc.h" +#include "utils/injection_point.h" #include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -54,6 +87,7 @@ #include "utils/relmapper.h" #include "utils/snapmgr.h" #include "utils/syscache.h" +#include "utils/wait_event_types.h" /* * This struct is used to pass around the information on tables to be @@ -66,12 +100,171 @@ typedef struct Oid indexOid; } RelToCluster; +/* + * The following definitions are used for concurrent processing. + */ + +/* + * The locators are used to avoid logical decoding of data that we do not need + * for our table. + */ +static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid}; +static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid}; + +/* + * Everything we need to call ExecInsertIndexTuples(). + */ +typedef struct IndexInsertState +{ + ResultRelInfo *rri; + EState *estate; +} IndexInsertState; + +/* The WAL segment being decoded. */ +static XLogSegNo repack_current_segment = 0; + +/* + * The first file exported by the decoding worker must contain a snapshot, the + * following ones contain the data changes. + */ +#define WORKER_FILE_SNAPSHOT 0 + +/* + * Information needed to apply concurrent data changes. + */ +typedef struct ChangeDest +{ + /* The relation the changes are applied to. */ + Relation rel; + + /* + * The following is needed to find the existing tuple if the change is + * UPDATE or DELETE. 'ident_key' should have all the fields except for + * 'sk_argument' initialized. + */ + Relation ident_index; + ScanKey ident_key; + int ident_key_nentries; + + /* Needed to update indexes of rel_dst. */ + IndexInsertState *iistate; + + /* + * Sequential number of the file containing the changes. + * + * TODO This field makes the structure name less descriptive. Should we + * rename it, e.g. to ChangeApplyInfo? + */ + int file_seq; +} ChangeDest; + +/* + * Layout of shared memory used for communication between backend and the + * worker that performs logical decoding of data changes + */ +typedef struct DecodingWorkerShared +{ + /* Is the decoding initialized? */ + bool initialized; + + /* + * Once the worker has reached this LSN, it should close the current + * output file and either create a new one or exit, according to the field + * 'done'. If the value is InvalidXLogRecPtr, the worker should decode all + * the WAL available and keep checking this field. It is ok if the worker + * had already decoded records whose LSN is >= lsn_upto before this field + * has been set. + */ + XLogRecPtr lsn_upto; + + /* Exit after closing the current file? */ + bool done; + + /* The output is stored here. */ + SharedFileSet sfs; + + /* Number of the last file exported by the worker. */ + int last_exported; + + /* Synchronize access to the fields above. */ + slock_t mutex; + + /* Database to connect to. */ + Oid dbid; + + /* Role to connect as. */ + Oid roleid; + + /* Decode data changes of this relation. */ + Oid relid; + + /* The backend uses this to wait for the worker. */ + ConditionVariable cv; + + /* Info to signal the backend. */ + PGPROC *backend_proc; + pid_t backend_pid; + ProcNumber backend_proc_number; + + /* + * Memory the queue is located in. + * + * For considerations on the value see the comments of + * PARALLEL_ERROR_QUEUE_SIZE. + */ +#define REPACK_ERROR_QUEUE_SIZE 16384 + char error_queue[FLEXIBLE_ARRAY_MEMBER]; +} DecodingWorkerShared; + +/* + * Generate worker's output file name. If relations of the same 'relid' happen + * to be processed at the same time, they must be from different databases and + * therefore different backends must be involved. (PID is already present in + * the fileset name.) + */ +static inline void +DecodingWorkerFileName(char *fname, Oid relid, uint32 seq) +{ + snprintf(fname, MAXPGPATH, "%u-%u", relid, seq); +} + +/* + * Backend-local information to control the decoding worker. + */ +typedef struct DecodingWorker +{ + /* The worker. */ + BackgroundWorkerHandle *handle; + + /* DecodingWorkerShared is in this segment. */ + dsm_segment *seg; + + /* Handle of the error queue. */ + shm_mq_handle *error_mqh; +} DecodingWorker; + +/* Pointer to currently running decoding worker. */ +static DecodingWorker *decoding_worker = NULL; + +/* + * Is there a message sent by a repack worker that the backend needs to + * receive? + */ +volatile sig_atomic_t RepackMessagePending = false; + static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, - Oid indexOid, Oid userid, int options); -static void rebuild_relation(Relation OldHeap, Relation index, bool verbose); + Oid indexOid, Oid userid, LOCKMODE lmode, + int options); +static void check_repack_concurrently_requirements(Relation rel, + Oid *ident_idx_p); +static void rebuild_relation(Relation OldHeap, Relation index, bool verbose, + Oid ident_idx); static void copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, - bool verbose, bool *pSwapToastByContent, - TransactionId *pFreezeXid, MultiXactId *pCutoffMulti); + Snapshot snapshot, + bool verbose, + bool *pSwapToastByContent, + TransactionId *pFreezeXid, + MultiXactId *pCutoffMulti); static List *get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt); static List *get_tables_to_repack_partitioned(RepackCommand cmd, @@ -79,13 +272,55 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd, MemoryContext permcxt); static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid); + +static LogicalDecodingContext *setup_logical_decoding(Oid relid); +static bool decode_concurrent_changes(LogicalDecodingContext *ctx, + DecodingWorkerShared *shared); +static void apply_concurrent_changes(BufFile *file, ChangeDest *dest); +static void apply_concurrent_insert(Relation rel, HeapTuple tup, + IndexInsertState *iistate, + TupleTableSlot *index_slot); +static void apply_concurrent_update(Relation rel, HeapTuple tup, + HeapTuple tup_target, + IndexInsertState *iistate, + TupleTableSlot *index_slot); +static void apply_concurrent_delete(Relation rel, HeapTuple tup_target); +static HeapTuple find_target_tuple(Relation rel, ChangeDest *dest, + HeapTuple tup_key, + TupleTableSlot *ident_slot); +static void process_concurrent_changes(XLogRecPtr end_of_wal, + ChangeDest *dest, + bool done); +static IndexInsertState *get_index_insert_state(Relation relation, + Oid ident_index_id, + Relation *ident_index_p); +static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src, + int *nentries); +static void free_index_insert_state(IndexInsertState *iistate); +static void cleanup_logical_decoding(LogicalDecodingContext *ctx); +static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap, + Oid identIdx, + TransactionId frozenXid, + MultiXactId cutoffMulti); +static List *build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes); static Relation process_single_relation(RepackStmt *stmt, + LOCKMODE lockmode, + bool isTopLevel, ClusterParams *params); static Oid determine_clustered_index(Relation rel, bool usingindex, const char *indexname); +static void start_decoding_worker(Oid relid); +static void stop_decoding_worker(void); +static void repack_worker_internal(dsm_segment *seg); +static void export_initial_snapshot(Snapshot snapshot, + DecodingWorkerShared *shared); +static Snapshot get_initial_snapshot(DecodingWorker *worker); +static void ProcessRepackMessage(StringInfo msg); static const char *RepackCommandAsString(RepackCommand cmd); +#define REPL_PLUGIN_NAME "pgoutput_repack" + /* * The repack code allows for processing multiple tables at once. Because * of this, we cannot just run everything on a single transaction, or we @@ -115,6 +350,7 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) ClusterParams params = {0}; Relation rel = NULL; MemoryContext repack_context; + LOCKMODE lockmode; List *rtcs; /* Parse option list */ @@ -125,6 +361,16 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) else if (strcmp(opt->defname, "analyze") == 0 || strcmp(opt->defname, "analyse") == 0) params.options |= defGetBoolean(opt) ? CLUOPT_ANALYZE : 0; + else if (strcmp(opt->defname, "concurrently") == 0 && + defGetBoolean(opt)) + { + if (stmt->command != REPACK_COMMAND_REPACK) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("CONCURRENTLY option not supported for %s", + RepackCommandAsString(stmt->command))); + params.options |= CLUOPT_CONCURRENT; + } else ereport(ERROR, errcode(ERRCODE_SYNTAX_ERROR), @@ -134,13 +380,25 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) parser_errposition(pstate, opt->location)); } + /* + * Determine the lock mode expected by cluster_rel(). + * + * In the exclusive case, we obtain AccessExclusiveLock right away to + * avoid lock-upgrade hazard in the single-transaction case. In the + * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end + * of processing, supposedly for very short time. Until then, we'll have + * to unlock the relation temporarily, so there's no lock-upgrade hazard. + */ + lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ? + AccessExclusiveLock : ShareUpdateExclusiveLock; + /* * If a single relation is specified, process it and we're done ... unless * the relation is a partitioned table, in which case we fall through. */ if (stmt->relation != NULL) { - rel = process_single_relation(stmt, ¶ms); + rel = process_single_relation(stmt, lockmode, isTopLevel, ¶ms); if (rel == NULL) return; /* all done */ } @@ -156,10 +414,29 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) "REPACK (ANALYZE)")); /* - * By here, we know we are in a multi-table situation. In order to avoid - * holding locks for too long, we want to process each table in its own - * transaction. This forces us to disallow running inside a user - * transaction block. + * By here, we know we are in a multi-table situation. + * + * Concurrent processing is currently considered rather special (e.g. in + * terms of resources consumed) so it is not performed in bulk. + */ + if (params.options & CLUOPT_CONCURRENT) + { + if (rel != NULL) + { + Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); + ereport(ERROR, + errmsg("REPACK CONCURRENTLY not supported for partitioned tables"), + errhint("Consider running the command for individual partitions.")); + } + else + ereport(ERROR, + errmsg("REPACK CONCURRENTLY requires explicit table name")); + } + + /* + * In order to avoid holding locks for too long, we want to process each + * table in its own transaction. This forces us to disallow running + * inside a user transaction block. */ PreventInTransactionBlock(isTopLevel, RepackCommandAsString(stmt->command)); @@ -253,7 +530,7 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) * Open the target table, coping with the case where it has been * dropped. */ - rel = try_table_open(rtc->tableOid, AccessExclusiveLock); + rel = try_table_open(rtc->tableOid, lockmode); if (rel == NULL) { CommitTransactionCommand(); @@ -264,7 +541,7 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) PushActiveSnapshot(GetTransactionSnapshot()); /* Process this table */ - cluster_rel(stmt->command, rel, rtc->indexOid, ¶ms); + cluster_rel(stmt->command, rel, rtc->indexOid, ¶ms, isTopLevel); /* cluster_rel closes the relation, but keeps lock */ PopActiveSnapshot(); @@ -293,22 +570,54 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) * If indexOid is InvalidOid, the table will be rewritten in physical order * instead of index order. * + * Note that, in the concurrent case, the function releases the lock at some + * point, in order to get AccessExclusiveLock for the final steps (i.e. to + * swap the relation files). To make things simpler, the caller should expect + * OldHeap to be closed on return, regardless CLUOPT_CONCURRENT. (The + * AccessExclusiveLock is kept till the end of the transaction.) + * * 'cmd' indicates which command is being executed, to be used for error * messages. */ void cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid, - ClusterParams *params) + ClusterParams *params, bool isTopLevel) { Oid tableOid = RelationGetRelid(OldHeap); + Relation index; + LOCKMODE lmode; Oid save_userid; int save_sec_context; int save_nestlevel; bool verbose = ((params->options & CLUOPT_VERBOSE) != 0); bool recheck = ((params->options & CLUOPT_RECHECK) != 0); - Relation index; + bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0); + Oid ident_idx = InvalidOid; + + /* + * The lock mode is AccessExclusiveLock for normal processing and + * ShareUpdateExclusiveLock for concurrent processing (so that SELECT, + * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be + * called concurrently for the same relation). + */ + lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock; + + /* There are specific requirements on concurrent processing. */ + if (concurrent) + { + /* + * Make sure we have no XID assigned, otherwise call of + * setup_logical_decoding() can cause a deadlock. + * + * The existence of transaction block actually does not imply that XID + * was already assigned, but it very likely is. We might want to check + * the result of GetCurrentTransactionIdIfAny() instead, but that + * would be less clear from user's perspective. + */ + PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)"); - Assert(CheckRelationLockedByMe(OldHeap, AccessExclusiveLock, false)); + check_repack_concurrently_requirements(OldHeap, &ident_idx); + } /* Check for user-requested abort. */ CHECK_FOR_INTERRUPTS(); @@ -334,10 +643,13 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid, * If this is a single-transaction CLUSTER, we can skip these tests. We * *must* skip the one on indisclustered since it would reject an attempt * to cluster a not-previously-clustered index. + * + * XXX move [some of] these comments to where the RECHECK flag is + * determined? */ if (recheck && !cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid, - params->options)) + lmode, params->options)) goto out; /* @@ -353,6 +665,12 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid, errmsg("cannot execute %s on a shared catalog", RepackCommandAsString(cmd))); + /* + * The CONCURRENTLY case should have been rejected earlier because it does + * not support system catalogs. + */ + Assert(!(OldHeap->rd_rel->relisshared && concurrent)); + /* * Don't process temp tables of other backends ... their local buffer * manager is not going to cope. @@ -374,7 +692,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid, if (OidIsValid(indexOid)) { /* verify the index is good and lock it */ - check_index_is_clusterable(OldHeap, indexOid, AccessExclusiveLock); + check_index_is_clusterable(OldHeap, indexOid, lmode); /* also open it */ index = index_open(indexOid, NoLock); } @@ -409,7 +727,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid, if (OldHeap->rd_rel->relkind == RELKIND_MATVIEW && !RelationIsPopulated(OldHeap)) { - relation_close(OldHeap, AccessExclusiveLock); + if (index) + index_close(index, lmode); + relation_close(OldHeap, lmode); goto out; } @@ -422,11 +742,34 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid, * invalid, because we move tuples around. Promote them to relation * locks. Predicate locks on indexes will be promoted when they are * reindexed. + * + * During concurrent processing, the heap as well as its indexes stay in + * operation, so we postpone this step until they are locked using + * AccessExclusiveLock near the end of the processing. */ - TransferPredicateLocksToHeapRelation(OldHeap); + if (!concurrent) + TransferPredicateLocksToHeapRelation(OldHeap); /* rebuild_relation does all the dirty work */ - rebuild_relation(OldHeap, index, verbose); + PG_TRY(); + { + rebuild_relation(OldHeap, index, verbose, ident_idx); + } + PG_FINALLY(); + { + if (concurrent) + { + /* + * Since during normal operation the worker was already asked to + * exit, stopping it explicitly is especially important on ERROR. + * However it still seems a good practice to make sure that the + * worker never survives the REPACK command. + */ + stop_decoding_worker(); + } + } + PG_END_TRY(); + /* rebuild_relation closes OldHeap, and index if valid */ out: @@ -445,14 +788,14 @@ out: */ static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid, - Oid userid, int options) + Oid userid, LOCKMODE lmode, int options) { Oid tableOid = RelationGetRelid(OldHeap); /* Check that the user still has privileges for the relation */ if (!repack_is_permitted_for_relation(cmd, tableOid, userid)) { - relation_close(OldHeap, AccessExclusiveLock); + relation_close(OldHeap, lmode); return false; } @@ -466,7 +809,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid, */ if (RELATION_IS_OTHER_TEMP(OldHeap)) { - relation_close(OldHeap, AccessExclusiveLock); + relation_close(OldHeap, lmode); return false; } @@ -477,7 +820,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid, */ if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(indexOid))) { - relation_close(OldHeap, AccessExclusiveLock); + relation_close(OldHeap, lmode); return false; } @@ -488,7 +831,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid, if ((options & CLUOPT_RECHECK_ISCLUSTERED) != 0 && !get_index_isclustered(indexOid)) { - relation_close(OldHeap, AccessExclusiveLock); + relation_close(OldHeap, lmode); return false; } } @@ -500,7 +843,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid, * Verify that the specified heap and index are valid to cluster on * * Side effect: obtains lock on the index. The caller may - * in some cases already have AccessExclusiveLock on the table, but + * in some cases already have a lock of the same strength on the table, but * not in all cases so we can't rely on the table-level lock for * protection here. */ @@ -629,18 +972,95 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal) table_close(pg_index, RowExclusiveLock); } +/* + * Check if the CONCURRENTLY option is legal for the relation. + * + * *Ident_idx_p receives OID of the identity index. + */ +static void +check_repack_concurrently_requirements(Relation rel, Oid *ident_idx_p) +{ + char relpersistence, + replident; + Oid ident_idx; + + /* Data changes in system relations are not logically decoded. */ + if (IsCatalogRelation(rel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot repack relation \"%s\"", + RelationGetRelationName(rel)), + errhint("REPACK CONCURRENTLY is not supported for catalog relations."))); + + /* + * reorderbuffer.c does not seem to handle processing of TOAST relation + * alone. + */ + if (IsToastRelation(rel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot repack relation \"%s\"", + RelationGetRelationName(rel)), + errhint("REPACK CONCURRENTLY is not supported for TOAST relations, unless the main relation is repacked too."))); + + relpersistence = rel->rd_rel->relpersistence; + if (relpersistence != RELPERSISTENCE_PERMANENT) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot repack relation \"%s\"", + RelationGetRelationName(rel)), + errhint("REPACK CONCURRENTLY is only allowed for permanent relations."))); + + /* With NOTHING, WAL does not contain the old tuple. */ + replident = rel->rd_rel->relreplident; + if (replident == REPLICA_IDENTITY_NOTHING) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot repack relation \"%s\"", + RelationGetRelationName(rel)), + errhint("Relation \"%s\" has insufficient replication identity.", + RelationGetRelationName(rel)))); + + /* + * If the identity index is not set due to replica identity being, PK + * might exist. + */ + ident_idx = RelationGetReplicaIndex(rel); + if (!OidIsValid(ident_idx) && OidIsValid(rel->rd_pkindex)) + ident_idx = rel->rd_pkindex; + if (!OidIsValid(ident_idx)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot process relation \"%s\"", + RelationGetRelationName(rel)), + errhint("Relation \"%s\" has no identity index.", + RelationGetRelationName(rel)))); + + *ident_idx_p = ident_idx; +} + + /* * rebuild_relation: rebuild an existing relation in index or physical order * - * OldHeap: table to rebuild. + * OldHeap: table to rebuild. See cluster_rel() for comments on the required + * lock strength. + * * index: index to cluster by, or NULL to rewrite in physical order. * - * On entry, heap and index (if one is given) must be open, and - * AccessExclusiveLock held on them. - * On exit, they are closed, but locks on them are not released. + * ident_idx: identity index, to handle replaying of concurrent data changes + * to the new heap. InvalidOid if there's no CONCURRENTLY option. + * + * On entry, heap and index (if one is given) must be open, and the + * appropriate lock held on them -- AccessExclusiveLock for exclusive + * processing and ShareUpdateExclusiveLock for concurrent processing. + * + * On exit, they are closed, but still locked with AccessExclusiveLock. + * (The function handles the lock upgrade if 'concurrent' is true.) */ static void -rebuild_relation(Relation OldHeap, Relation index, bool verbose) +rebuild_relation(Relation OldHeap, Relation index, bool verbose, + Oid ident_idx) { Oid tableOid = RelationGetRelid(OldHeap); Oid accessMethod = OldHeap->rd_rel->relam; @@ -648,13 +1068,55 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose) Oid OIDNewHeap; Relation NewHeap; char relpersistence; - bool is_system_catalog; bool swap_toast_by_content; TransactionId frozenXid; MultiXactId cutoffMulti; + bool concurrent = OidIsValid(ident_idx); + Snapshot snapshot = NULL; +#if USE_ASSERT_CHECKING + LOCKMODE lmode; + + lmode = concurrent ? ShareUpdateExclusiveLock : AccessExclusiveLock; + + Assert(CheckRelationLockedByMe(OldHeap, lmode, false)); + Assert(index == NULL || CheckRelationLockedByMe(index, lmode, false)); +#endif + + if (concurrent) + { + /* + * The worker needs to be member of the locking group we're the leader + * of. We ought to become the leader before the worker starts. The + * worker will join the group as soon as it starts. + * + * This is to make sure that the deadlock described below is + * detectable by deadlock.c: if the worker waits for a transaction to + * complete and we are waiting for the worker output, then effectively + * we (i.e. this backend) are waiting for that transaction. + */ + BecomeLockGroupLeader(); + + /* + * Start the worker that decodes data changes applied while we're + * copying the table contents. + * + * Note that the worker has to wait for all transactions with XID + * already assigned to finish. If some of those transactions is + * waiting for a lock conflicting with ShareUpdateExclusiveLock on our + * table (e.g. it runs CREATE INDEX), we can end up in a deadlock. + * Not sure this risk is worth unlocking/locking the table (and its + * clustering index) and checking again if it's still eligible for + * REPACK CONCURRENTLY. + */ + start_decoding_worker(tableOid); + + /* + * Wait until the worker has the initial snapshot and retrieve it. + */ + snapshot = get_initial_snapshot(decoding_worker); - Assert(CheckRelationLockedByMe(OldHeap, AccessExclusiveLock, false) && - (index == NULL || CheckRelationLockedByMe(index, AccessExclusiveLock, false))); + PushActiveSnapshot(snapshot); + } /* for CLUSTER or REPACK USING INDEX, mark the index as the one to use */ if (index != NULL) @@ -662,7 +1124,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose) /* Remember info about rel before closing OldHeap */ relpersistence = OldHeap->rd_rel->relpersistence; - is_system_catalog = IsSystemRelation(OldHeap); /* * Create the transient table that will receive the re-ordered data. @@ -678,30 +1139,59 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose) NewHeap = table_open(OIDNewHeap, NoLock); /* Copy the heap data into the new table in the desired order */ - copy_table_data(NewHeap, OldHeap, index, verbose, + copy_table_data(NewHeap, OldHeap, index, snapshot, verbose, &swap_toast_by_content, &frozenXid, &cutoffMulti); + /* The historic snapshot won't be needed anymore. */ + if (snapshot) + { + PopActiveSnapshot(); + UpdateActiveSnapshotCommandId(); + } - /* Close relcache entries, but keep lock until transaction commit */ - table_close(OldHeap, NoLock); - if (index) - index_close(index, NoLock); + if (concurrent) + { + Assert(!swap_toast_by_content); - /* - * Close the new relation so it can be dropped as soon as the storage is - * swapped. The relation is not visible to others, so no need to unlock it - * explicitly. - */ - table_close(NewHeap, NoLock); + /* + * Close the index, but keep the lock. Both heaps will be closed by + * the following call. + */ + if (index) + index_close(index, NoLock); - /* - * Swap the physical files of the target and transient tables, then - * rebuild the target's indexes and throw away the transient table. - */ - finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog, - swap_toast_by_content, false, true, - frozenXid, cutoffMulti, - relpersistence); + rebuild_relation_finish_concurrent(NewHeap, OldHeap, ident_idx, + frozenXid, cutoffMulti); + + pgstat_progress_update_param(PROGRESS_REPACK_PHASE, + PROGRESS_REPACK_PHASE_FINAL_CLEANUP); + } + else + { + bool is_system_catalog = IsSystemRelation(OldHeap); + + /* Close relcache entries, but keep lock until transaction commit */ + table_close(OldHeap, NoLock); + if (index) + index_close(index, NoLock); + + /* + * Close the new relation so it can be dropped as soon as the storage + * is swapped. The relation is not visible to others, so no need to + * unlock it explicitly. + */ + table_close(NewHeap, NoLock); + + /* + * Swap the physical files of the target and transient tables, then + * rebuild the target's indexes and throw away the transient table. + */ + finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog, + swap_toast_by_content, false, true, + true, /* reindex */ + frozenXid, cutoffMulti, + relpersistence); + } } @@ -836,15 +1326,18 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, /* * Do the physical copying of table data. * + * 'snapshot' and 'decoding_ctx': see table_relation_copy_for_cluster(). Pass + * iff concurrent processing is required. + * * There are three output parameters: * *pSwapToastByContent is set true if toast tables must be swapped by content. * *pFreezeXid receives the TransactionId used as freeze cutoff point. * *pCutoffMulti receives the MultiXactId used as a cutoff point. */ static void -copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verbose, - bool *pSwapToastByContent, TransactionId *pFreezeXid, - MultiXactId *pCutoffMulti) +copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, + Snapshot snapshot, bool verbose, bool *pSwapToastByContent, + TransactionId *pFreezeXid, MultiXactId *pCutoffMulti) { Relation relRelation; HeapTuple reltup; @@ -861,6 +1354,10 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb int elevel = verbose ? INFO : DEBUG2; PGRUsage ru0; char *nspname; + bool concurrent = snapshot != NULL; + LOCKMODE lmode; + + lmode = concurrent ? ShareUpdateExclusiveLock : AccessExclusiveLock; pg_rusage_init(&ru0); @@ -889,7 +1386,7 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb * will be held till end of transaction. */ if (OldHeap->rd_rel->reltoastrelid) - LockRelationOid(OldHeap->rd_rel->reltoastrelid, AccessExclusiveLock); + LockRelationOid(OldHeap->rd_rel->reltoastrelid, lmode); /* * If both tables have TOAST tables, perform toast swap by content. It is @@ -898,7 +1395,8 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb * swap by links. This is okay because swap by content is only essential * for system catalogs, and we don't support schema changes for them. */ - if (OldHeap->rd_rel->reltoastrelid && NewHeap->rd_rel->reltoastrelid) + if (OldHeap->rd_rel->reltoastrelid && NewHeap->rd_rel->reltoastrelid && + !concurrent) { *pSwapToastByContent = true; @@ -919,6 +1417,10 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb * follow the toast pointers to the wrong place. (It would actually * work for values copied over from the old toast table, but not for * any values that we toast which were previously not toasted.) + * + * This would not work with CONCURRENTLY because we may need to delete + * TOASTed tuples from the new heap. With this hack, we'd delete them + * from the old heap. */ NewHeap->rd_toastoid = OldHeap->rd_rel->reltoastrelid; } @@ -994,7 +1496,8 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb * values (e.g. because the AM doesn't use freezing). */ table_relation_copy_for_cluster(OldHeap, NewHeap, OldIndex, use_sort, - cutoffs.OldestXmin, &cutoffs.FreezeLimit, + cutoffs.OldestXmin, snapshot, + &cutoffs.FreezeLimit, &cutoffs.MultiXactCutoff, &num_tuples, &tups_vacuumed, &tups_recently_dead); @@ -1003,7 +1506,11 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb *pFreezeXid = cutoffs.FreezeLimit; *pCutoffMulti = cutoffs.MultiXactCutoff; - /* Reset rd_toastoid just to be tidy --- it shouldn't be looked at again */ + /* + * Reset rd_toastoid just to be tidy --- it shouldn't be looked at again. + * In the CONCURRENTLY case, we need to set it again before applying the + * concurrent changes. + */ NewHeap->rd_toastoid = InvalidOid; num_pages = RelationGetNumberOfBlocks(NewHeap); @@ -1461,14 +1968,13 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool swap_toast_by_content, bool check_constraints, bool is_internal, + bool reindex, TransactionId frozenXid, MultiXactId cutoffMulti, char newrelpersistence) { ObjectAddress object; Oid mapped_tables[4]; - int reindex_flags; - ReindexParams reindex_params = {0}; int i; /* Report that we are now swapping relation files */ @@ -1494,39 +2000,47 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, if (is_system_catalog) CacheInvalidateCatalog(OIDOldHeap); - /* - * Rebuild each index on the relation (but not the toast table, which is - * all-new at this point). It is important to do this before the DROP - * step because if we are processing a system catalog that will be used - * during DROP, we want to have its indexes available. There is no - * advantage to the other order anyway because this is all transactional, - * so no chance to reclaim disk space before commit. We do not need a - * final CommandCounterIncrement() because reindex_relation does it. - * - * Note: because index_build is called via reindex_relation, it will never - * set indcheckxmin true for the indexes. This is OK even though in some - * sense we are building new indexes rather than rebuilding existing ones, - * because the new heap won't contain any HOT chains at all, let alone - * broken ones, so it can't be necessary to set indcheckxmin. - */ - reindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE; - if (check_constraints) - reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS; + if (reindex) + { + int reindex_flags; + ReindexParams reindex_params = {0}; - /* - * Ensure that the indexes have the same persistence as the parent - * relation. - */ - if (newrelpersistence == RELPERSISTENCE_UNLOGGED) - reindex_flags |= REINDEX_REL_FORCE_INDEXES_UNLOGGED; - else if (newrelpersistence == RELPERSISTENCE_PERMANENT) - reindex_flags |= REINDEX_REL_FORCE_INDEXES_PERMANENT; + /* + * Rebuild each index on the relation (but not the toast table, which + * is all-new at this point). It is important to do this before the + * DROP step because if we are processing a system catalog that will + * be used during DROP, we want to have its indexes available. There + * is no advantage to the other order anyway because this is all + * transactional, so no chance to reclaim disk space before commit. We + * do not need a final CommandCounterIncrement() because + * reindex_relation does it. + * + * Note: because index_build is called via reindex_relation, it will + * never set indcheckxmin true for the indexes. This is OK even + * though in some sense we are building new indexes rather than + * rebuilding existing ones, because the new heap won't contain any + * HOT chains at all, let alone broken ones, so it can't be necessary + * to set indcheckxmin. + */ + reindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE; + if (check_constraints) + reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS; - /* Report that we are now reindexing relations */ - pgstat_progress_update_param(PROGRESS_REPACK_PHASE, - PROGRESS_REPACK_PHASE_REBUILD_INDEX); + /* + * Ensure that the indexes have the same persistence as the parent + * relation. + */ + if (newrelpersistence == RELPERSISTENCE_UNLOGGED) + reindex_flags |= REINDEX_REL_FORCE_INDEXES_UNLOGGED; + else if (newrelpersistence == RELPERSISTENCE_PERMANENT) + reindex_flags |= REINDEX_REL_FORCE_INDEXES_PERMANENT; - reindex_relation(NULL, OIDOldHeap, reindex_flags, &reindex_params); + /* Report that we are now reindexing relations */ + pgstat_progress_update_param(PROGRESS_REPACK_PHASE, + PROGRESS_REPACK_PHASE_REBUILD_INDEX); + + reindex_relation(NULL, OIDOldHeap, reindex_flags, &reindex_params); + } /* Report that we are now doing clean up */ pgstat_progress_update_param(PROGRESS_REPACK_PHASE, @@ -1570,6 +2084,17 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, object.objectId = OIDNewHeap; object.objectSubId = 0; + if (!reindex) + { + /* + * Make sure the changes in pg_class are visible. This is especially + * important if !swap_toast_by_content, so that the correct TOAST + * relation is dropped. (reindex_relation() above did not help in this + * case)) + */ + CommandCounterIncrement(); + } + /* * The new relation is local to our transaction and we know nothing * depends on it, so DROP_RESTRICT should be OK. @@ -1609,7 +2134,7 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, /* Get the associated valid index to be renamed */ toastidx = toast_get_valid_index(newrel->rd_rel->reltoastrelid, - NoLock); + AccessExclusiveLock); /* rename the toast table ... */ snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u", @@ -1880,7 +2405,8 @@ repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) * case, if an index name is given, it's up to the caller to resolve it. */ static Relation -process_single_relation(RepackStmt *stmt, ClusterParams *params) +process_single_relation(RepackStmt *stmt, LOCKMODE lockmode, bool isTopLevel, + ClusterParams *params) { Relation rel; Oid tableOid; @@ -1897,13 +2423,9 @@ process_single_relation(RepackStmt *stmt, ClusterParams *params) errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("ANALYZE option must be specified when a column list is provided")); - /* - * Find, lock, and check permissions on the table. We obtain - * AccessExclusiveLock right away to avoid lock-upgrade hazard in the - * single-transaction case. - */ + /* Find, lock, and check permissions on the table. */ tableOid = RangeVarGetRelidExtended(stmt->relation->relation, - AccessExclusiveLock, + lockmode, 0, RangeVarCallbackMaintainsTable, NULL); @@ -1928,13 +2450,14 @@ process_single_relation(RepackStmt *stmt, ClusterParams *params) return rel; else { - Oid indexOid; + Oid indexOid = InvalidOid; indexOid = determine_clustered_index(rel, stmt->usingindex, stmt->indexname); if (OidIsValid(indexOid)) - check_index_is_clusterable(rel, indexOid, AccessExclusiveLock); - cluster_rel(stmt->command, rel, indexOid, params); + check_index_is_clusterable(rel, indexOid, lockmode); + + cluster_rel(stmt->command, rel, indexOid, params, isTopLevel); /* * Do an analyze, if requested. We close the transaction and start a @@ -2029,3 +2552,1569 @@ RepackCommandAsString(RepackCommand cmd) } return "???"; /* keep compiler quiet */ } + + +/* + * Is this backend performing logical decoding on behalf of REPACK + * (CONCURRENTLY) ? + */ +bool +am_decoding_for_repack(void) +{ + return OidIsValid(repacked_rel_locator.relNumber); +} + +/* + * Does the WAL record contain a data change that this backend does not need + * to decode on behalf of REPACK (CONCURRENTLY)? + */ +bool +change_useless_for_repack(XLogRecordBuffer *buf) +{ + XLogReaderState *r = buf->record; + RelFileLocator locator; + + /* TOAST locator should not be set unless the main is. */ + Assert(!OidIsValid(repacked_rel_toast_locator.relNumber) || + OidIsValid(repacked_rel_locator.relNumber)); + + /* + * Backends not involved in REPACK (CONCURRENTLY) should not do the + * filtering. + */ + if (!am_decoding_for_repack()) + return false; + + /* + * If the record does not contain the block 0, it's probably not INSERT / + * UPDATE / DELETE. In any case, we do not have enough information to + * filter the change out. + */ + if (!XLogRecGetBlockTagExtended(r, 0, &locator, NULL, NULL, NULL)) + return false; + + /* + * Decode the change if it belongs to the table we are repacking, or if it + * belongs to its TOAST relation. + */ + if (RelFileLocatorEquals(locator, repacked_rel_locator)) + return false; + if (OidIsValid(repacked_rel_toast_locator.relNumber) && + RelFileLocatorEquals(locator, repacked_rel_toast_locator)) + return false; + + /* Filter out changes of other tables. */ + return true; +} + +/* + * This function is much like pg_create_logical_replication_slot() except that + * the new slot is neither released (if anyone else could read changes from + * our slot, we could miss changes other backends do while we copy the + * existing data into temporary table), nor persisted (it's easier to handle + * crash by restarting all the work from scratch). + */ +static LogicalDecodingContext * +setup_logical_decoding(Oid relid) +{ + Relation rel; + Oid toastrelid; + LogicalDecodingContext *ctx; + NameData slotname; + RepackDecodingState *dstate; + + /* + * REPACK CONCURRENTLY is not allowed in a transaction block, so this + * should never fire. + */ + Assert(!TransactionIdIsValid(GetTopTransactionIdIfAny())); + + /* + * Make sure we can use logical decoding. + */ + CheckSlotPermissions(); + CheckLogicalDecodingRequirements(); + + /* + * A single backend should not execute multiple REPACK commands at a time, + * so use PID to make the slot unique. + * + * RS_TEMPORARY so that the slot gets cleaned up on ERROR. + */ + snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid); + ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false, + false); + + EnsureLogicalDecodingEnabled(); + + /* + * Neither prepare_write nor do_write callback nor update_progress is + * useful for us. + */ + ctx = CreateInitDecodingContext(REPL_PLUGIN_NAME, + NIL, + true, + InvalidXLogRecPtr, + XL_ROUTINE(.page_read = read_local_xlog_page, + .segment_open = wal_segment_open, + .segment_close = wal_segment_close), + NULL, NULL, NULL); + + /* + * We don't have control on setting fast_forward, so at least check it. + */ + Assert(!ctx->fast_forward); + + DecodingContextFindStartpoint(ctx); + + /* + * decode_concurrent_changes() needs non-blocking callback. + */ + ctx->reader->routine.page_read = read_local_xlog_page_no_wait; + + /* + * read_local_xlog_page_no_wait() needs to be able to indicate the end of + * WAL. + */ + ctx->reader->private_data = MemoryContextAllocZero(ctx->context, + sizeof(ReadLocalXLogPageNoWaitPrivate)); + + + /* Some WAL records should have been read. */ + Assert(ctx->reader->EndRecPtr != InvalidXLogRecPtr); + + /* + * Initialize repack_current_segment so that we can notice WAL segment + * boundaries. + */ + XLByteToSeg(ctx->reader->EndRecPtr, repack_current_segment, + wal_segment_size); + + dstate = palloc0_object(RepackDecodingState); + dstate->relid = relid; + + /* + * Tuple descriptor may be needed to flatten a tuple before we write it to + * a file. A copy is needed because the decoding worker invalidates system + * caches before it starts to do the actual work. + */ + rel = table_open(relid, AccessShareLock); + dstate->tupdesc = CreateTupleDescCopy(RelationGetDescr(rel)); + + /* Avoid logical decoding of other relations. */ + repacked_rel_locator = rel->rd_locator; + toastrelid = rel->rd_rel->reltoastrelid; + if (OidIsValid(toastrelid)) + { + Relation toastrel; + + /* Avoid logical decoding of other TOAST relations. */ + toastrel = table_open(toastrelid, AccessShareLock); + repacked_rel_toast_locator = toastrel->rd_locator; + table_close(toastrel, AccessShareLock); + } + table_close(rel, AccessShareLock); + + /* The file will be set as soon as we have it opened. */ + dstate->file = NULL; + + ctx->output_writer_private = dstate; + + return ctx; +} + +/* + * Decode logical changes from the WAL sequence and store them to a file. + * + * If true is returned, there is no more work for the worker. + */ +static bool +decode_concurrent_changes(LogicalDecodingContext *ctx, + DecodingWorkerShared *shared) +{ + RepackDecodingState *dstate; + XLogRecPtr lsn_upto; + bool done; + char fname[MAXPGPATH]; + + dstate = (RepackDecodingState *) ctx->output_writer_private; + + /* Open the output file. */ + DecodingWorkerFileName(fname, shared->relid, shared->last_exported + 1); + dstate->file = BufFileCreateFileSet(&shared->sfs.fs, fname); + + SpinLockAcquire(&shared->mutex); + lsn_upto = shared->lsn_upto; + done = shared->done; + SpinLockRelease(&shared->mutex); + + while (true) + { + XLogRecord *record; + XLogSegNo segno_new; + char *errm = NULL; + XLogRecPtr end_lsn; + + CHECK_FOR_INTERRUPTS(); + + record = XLogReadRecord(ctx->reader, &errm); + if (record) + { + LogicalDecodingProcessRecord(ctx, ctx->reader); + + /* + * If WAL segment boundary has been crossed, inform the decoding + * system that the catalog_xmin can advance. + * + * TODO Does it make sense to confirm more often? Segment size + * seems appropriate for restart_lsn (because less than a segment + * cannot be recycled anyway), however more frequent checks might + * be beneficial for catalog_xmin. + */ + end_lsn = ctx->reader->EndRecPtr; + XLByteToSeg(end_lsn, segno_new, wal_segment_size); + if (segno_new != repack_current_segment) + { + LogicalConfirmReceivedLocation(end_lsn); + elog(DEBUG1, "REPACK: confirmed receive location %X/%X", + (uint32) (end_lsn >> 32), (uint32) end_lsn); + repack_current_segment = segno_new; + } + } + else + { + ReadLocalXLogPageNoWaitPrivate *priv; + + if (errm) + ereport(ERROR, (errmsg("%s", errm))); + + /* + * In the decoding loop we do not want to get blocked when there + * is no more WAL available, otherwise the loop would become + * uninterruptible. + */ + priv = (ReadLocalXLogPageNoWaitPrivate *) + ctx->reader->private_data; + if (priv->end_of_wal) + /* Do not miss the end of WAL condition next time. */ + priv->end_of_wal = false; + else + ereport(ERROR, (errmsg("could not read WAL record"))); + } + + /* + * Whether we could read new record or not, keep checking if + * 'lsn_upto' was specified. + */ + if (XLogRecPtrIsInvalid(lsn_upto)) + { + SpinLockAcquire(&shared->mutex); + lsn_upto = shared->lsn_upto; + /* 'done' should be set at the same time as 'lsn_upto' */ + done = shared->done; + SpinLockRelease(&shared->mutex); + } + if (!XLogRecPtrIsInvalid(lsn_upto) && + ctx->reader->EndRecPtr >= lsn_upto) + break; + + if (record == NULL) + { + int64 timeout = 0; + WaitLSNResult res; + + /* + * Before we retry reading, wait until new WAL is flushed. + * + * There is a race condition such that the backend executing + * REPACK determines 'lsn_upto', but before it sets the shared + * variable, we reach the end of WAL. In that case we'd need to + * wait until the next WAL flush (unrelated to REPACK). Although + * that should not be a problem in a busy system, it might be + * noticeable in other cases, including regression tests (which + * are not necessarily executed in parallel). Therefore it makes + * sense to use timeout. + * + * If lsn_upto is valid, WAL records having LSN lower than that + * should already have been flushed to disk. + */ + if (XLogRecPtrIsInvalid(lsn_upto)) + timeout = 100L; + res = WaitForLSN(WAIT_LSN_TYPE_PRIMARY_FLUSH, + ctx->reader->EndRecPtr + 1, + timeout); + if (res != WAIT_LSN_RESULT_SUCCESS && + res != WAIT_LSN_RESULT_TIMEOUT) + ereport(ERROR, (errmsg("waiting for WAL failed"))); + } + } + + /* + * Close the file so we can make it available to the backend. + */ + BufFileClose(dstate->file); + dstate->file = NULL; + SpinLockAcquire(&shared->mutex); + shared->lsn_upto = InvalidXLogRecPtr; + shared->last_exported++; + SpinLockRelease(&shared->mutex); + ConditionVariableSignal(&shared->cv); + + return done; +} + +/* + * Apply changes stored in 'file'. + */ +static void +apply_concurrent_changes(BufFile *file, ChangeDest *dest) +{ + char kind; + uint32 t_len; + Relation rel = dest->rel; + TupleTableSlot *index_slot, + *ident_slot; + HeapTuple tup_old = NULL; + + /* TupleTableSlot is needed to pass the tuple to ExecInsertIndexTuples(). */ + index_slot = MakeSingleTupleTableSlot(RelationGetDescr(rel), + &TTSOpsHeapTuple); + + /* A slot to fetch tuples from identity index. */ + ident_slot = table_slot_create(rel, NULL); + + while (true) + { + size_t nread; + HeapTuple tup, + tup_exist; + + CHECK_FOR_INTERRUPTS(); + + nread = BufFileReadMaybeEOF(file, &kind, 1, true); + /* Are we done with the file? */ + if (nread == 0) + break; + + /* Read the tuple. */ + BufFileReadExact(file, &t_len, sizeof(t_len)); + tup = (HeapTuple) palloc(HEAPTUPLESIZE + t_len); + tup->t_data = (HeapTupleHeader) ((char *) tup + HEAPTUPLESIZE); + BufFileReadExact(file, tup->t_data, t_len); + tup->t_len = t_len; + ItemPointerSetInvalid(&tup->t_self); + tup->t_tableOid = RelationGetRelid(dest->rel); + + if (kind == CHANGE_UPDATE_OLD) + { + Assert(tup_old == NULL); + tup_old = tup; + } + else if (kind == CHANGE_INSERT) + { + Assert(tup_old == NULL); + + apply_concurrent_insert(rel, tup, dest->iistate, index_slot); + + pfree(tup); + } + else if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE) + { + HeapTuple tup_key; + + if (kind == CHANGE_UPDATE_NEW) + { + tup_key = tup_old != NULL ? tup_old : tup; + } + else + { + Assert(tup_old == NULL); + tup_key = tup; + } + + /* + * Find the tuple to be updated or deleted. + */ + tup_exist = find_target_tuple(rel, dest, tup_key, ident_slot); + if (tup_exist == NULL) + elog(ERROR, "failed to find target tuple"); + + if (kind == CHANGE_UPDATE_NEW) + apply_concurrent_update(rel, tup, tup_exist, dest->iistate, + index_slot); + else + apply_concurrent_delete(rel, tup_exist); + + if (tup_old != NULL) + { + pfree(tup_old); + tup_old = NULL; + } + + pfree(tup); + } + else + elog(ERROR, "unrecognized kind of change: %d", kind); + + /* + * If a change was applied now, increment CID for next writes and + * update the snapshot so it sees the changes we've applied so far. + */ + if (kind != CHANGE_UPDATE_OLD) + { + CommandCounterIncrement(); + UpdateActiveSnapshotCommandId(); + } + } + + /* Cleanup. */ + ExecDropSingleTupleTableSlot(index_slot); + ExecDropSingleTupleTableSlot(ident_slot); +} + +static void +apply_concurrent_insert(Relation rel, HeapTuple tup, IndexInsertState *iistate, + TupleTableSlot *index_slot) +{ + List *recheck; + + /* + * Like simple_heap_insert(), but make sure that the INSERT is not + * logically decoded - see reform_and_rewrite_tuple() for more + * information. + */ + heap_insert(rel, tup, GetCurrentCommandId(true), HEAP_INSERT_NO_LOGICAL, + NULL); + + /* + * Update indexes. + */ + ExecStoreHeapTuple(tup, index_slot, false); + recheck = ExecInsertIndexTuples(iistate->rri, + iistate->estate, + 0, + index_slot, + NIL, NULL); + + /* + * If recheck is required, it must have been performed on the source + * relation by now. (All the logical changes we process here are already + * committed.) + */ + list_free(recheck); + ResetPerTupleExprContext(iistate->estate); + + pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1); +} + +static void +apply_concurrent_update(Relation rel, HeapTuple tup, HeapTuple tup_target, + IndexInsertState *iistate, TupleTableSlot *index_slot) +{ + LockTupleMode lockmode; + TM_FailureData tmfd; + TU_UpdateIndexes update_indexes; + TM_Result res; + List *recheck; + + /* + * Write the new tuple into the new heap. ('tup' gets the TID assigned + * here.) + * + * Do it like in simple_heap_update(), except for 'wal_logical' (and + * except for 'wait'). + */ + res = heap_update(rel, &tup_target->t_self, tup, + GetCurrentCommandId(true), + InvalidSnapshot, + false, /* no wait - only we are doing changes */ + &tmfd, &lockmode, &update_indexes, + false /* wal_logical */ ); + if (res != TM_Ok) + ereport(ERROR, (errmsg("failed to apply concurrent UPDATE"))); + + ExecStoreHeapTuple(tup, index_slot, false); + + if (update_indexes != TU_None) + { + bits32 flags = EIIT_IS_UPDATE; + + if (update_indexes == TU_Summarizing) + flags |= EIIT_ONLY_SUMMARIZING; + recheck = ExecInsertIndexTuples(iistate->rri, + iistate->estate, + flags, + index_slot, + NIL, NULL); + list_free(recheck); + ResetPerTupleExprContext(iistate->estate); + } + + pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1); +} + +static void +apply_concurrent_delete(Relation rel, HeapTuple tup_target) +{ + TM_Result res; + TM_FailureData tmfd; + + /* + * Delete tuple from the new heap. + * + * Do it like in simple_heap_delete(), except for 'wal_logical' (and + * except for 'wait'). + */ + res = heap_delete(rel, &tup_target->t_self, GetCurrentCommandId(true), + InvalidSnapshot, false, + &tmfd, + false, /* no wait - only we are doing changes */ + false /* wal_logical */ ); + + if (res != TM_Ok) + ereport(ERROR, (errmsg("failed to apply concurrent DELETE"))); + + pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_DELETED, 1); +} + +/* + * Find the tuple to be updated or deleted. + * + * 'tup_key' is a tuple containing the key values for the scan. + */ +static HeapTuple +find_target_tuple(Relation rel, ChangeDest *dest, HeapTuple tup_key, + TupleTableSlot *ident_slot) +{ + Relation ident_index = dest->ident_index; + IndexScanDesc scan; + Form_pg_index ident_form; + int2vector *ident_indkey; + HeapTuple result = NULL; + + /* XXX no instrumentation for now */ + scan = index_beginscan(rel, ident_index, GetActiveSnapshot(), + NULL, dest->ident_key_nentries, 0); + + /* Info needed to retrieve key values from heap tuple. */ + ident_form = ident_index->rd_index; + ident_indkey = &ident_form->indkey; + + /* + * Scan key is passed by caller, so it does not have to be constructed + * multiple times. Key entries have all fields initialized, except for + * sk_argument. + * + * Use the incoming tuple to finalize the scan key. + */ + for (int i = 0; i < dest->ident_key_nentries; i++) + { + ScanKey entry; + bool isnull; + int16 attno_heap; + + entry = &dest->ident_key[i]; + attno_heap = ident_indkey->values[i]; + entry->sk_argument = heap_getattr(tup_key, + attno_heap, + rel->rd_att, + &isnull); + Assert(!isnull); + } + + index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0); + if (index_getnext_slot(scan, ForwardScanDirection, ident_slot)) + { + bool shouldFree; + + result = ExecFetchSlotHeapTuple(ident_slot, false, &shouldFree); + /* TTSOpsBufferHeapTuple has .get_heap_tuple != NULL. */ + Assert(!shouldFree); + } + index_endscan(scan); + + return result; +} + +/* + * Decode and apply concurrent changes, up to (and including) the record whose + * LSN is 'end_of_wal'. + */ +static void +process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done) +{ + DecodingWorkerShared *shared; + char fname[MAXPGPATH]; + BufFile *file; + + pgstat_progress_update_param(PROGRESS_REPACK_PHASE, + PROGRESS_REPACK_PHASE_CATCH_UP); + + /* Ask the worker for the file. */ + shared = (DecodingWorkerShared *) dsm_segment_address(decoding_worker->seg); + SpinLockAcquire(&shared->mutex); + shared->lsn_upto = end_of_wal; + shared->done = done; + SpinLockRelease(&shared->mutex); + + /* + * The worker needs to finish processing of the current WAL record. Even + * if it's idle, it'll need to close the output file. Thus we're likely to + * wait, so prepare for sleep. + */ + ConditionVariablePrepareToSleep(&shared->cv); + for (;;) + { + int last_exported; + + SpinLockAcquire(&shared->mutex); + last_exported = shared->last_exported; + SpinLockRelease(&shared->mutex); + + /* + * Has the worker exported the file we are waiting for? + */ + if (last_exported == dest->file_seq) + break; + + ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT); + } + ConditionVariableCancelSleep(); + + /* Open the file. */ + DecodingWorkerFileName(fname, shared->relid, dest->file_seq); + file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false); + apply_concurrent_changes(file, dest); + + BufFileClose(file); + + /* Get ready for the next file. */ + dest->file_seq++; +} + +/* + * Initialize IndexInsertState for index specified by ident_index_id. + * + * While doing that, also return the identity index in *ident_index_p. + */ +static IndexInsertState * +get_index_insert_state(Relation relation, Oid ident_index_id, + Relation *ident_index_p) +{ + EState *estate; + int i; + IndexInsertState *result; + Relation ident_index = NULL; + + result = (IndexInsertState *) palloc0(sizeof(IndexInsertState)); + estate = CreateExecutorState(); + + result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo)); + InitResultRelInfo(result->rri, relation, 0, 0, 0); + ExecOpenIndices(result->rri, false); + + /* + * Find the relcache entry of the identity index so that we spend no extra + * effort to open / close it. + */ + for (i = 0; i < result->rri->ri_NumIndices; i++) + { + Relation ind_rel; + + ind_rel = result->rri->ri_IndexRelationDescs[i]; + if (ind_rel->rd_id == ident_index_id) + ident_index = ind_rel; + } + if (ident_index == NULL) + elog(ERROR, "failed to open identity index"); + + /* Only initialize fields needed by ExecInsertIndexTuples(). */ + result->estate = estate; + + *ident_index_p = ident_index; + return result; +} + +/* + * Build scan key to process logical changes. + */ +static ScanKey +build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries) +{ + Relation ident_idx_rel; + Form_pg_index ident_idx; + int n, + i; + ScanKey result; + + Assert(OidIsValid(ident_idx_oid)); + ident_idx_rel = index_open(ident_idx_oid, AccessShareLock); + ident_idx = ident_idx_rel->rd_index; + n = ident_idx->indnkeyatts; + result = (ScanKey) palloc(sizeof(ScanKeyData) * n); + for (i = 0; i < n; i++) + { + ScanKey entry; + Oid opfamily, + opcintype, + opno, + opcode; + + entry = &result[i]; + + opfamily = ident_idx_rel->rd_opfamily[i]; + opcintype = ident_idx_rel->rd_opcintype[i]; + opno = get_opfamily_member(opfamily, opcintype, opcintype, + BTEqualStrategyNumber); + + if (!OidIsValid(opno)) + elog(ERROR, "failed to find = operator for type %u", opcintype); + + opcode = get_opcode(opno); + if (!OidIsValid(opcode)) + elog(ERROR, "failed to find = operator for operator %u", opno); + + /* Initialize everything but argument. */ + ScanKeyInit(entry, + i + 1, + BTEqualStrategyNumber, opcode, + (Datum) NULL); + entry->sk_collation = ident_idx_rel->rd_indcollation[i]; + } + index_close(ident_idx_rel, AccessShareLock); + + *nentries = n; + return result; +} + +static void +free_index_insert_state(IndexInsertState *iistate) +{ + ExecCloseIndices(iistate->rri); + FreeExecutorState(iistate->estate); + pfree(iistate->rri); + pfree(iistate); +} + +static void +cleanup_logical_decoding(LogicalDecodingContext *ctx) +{ + RepackDecodingState *dstate; + + dstate = (RepackDecodingState *) ctx->output_writer_private; + + FreeTupleDesc(dstate->tupdesc); + FreeDecodingContext(ctx); + + ReplicationSlotDropAcquired(); + pfree(dstate); +} + +/* + * The final steps of rebuild_relation() for concurrent processing. + * + * On entry, NewHeap is locked in AccessExclusiveLock mode. OldHeap and its + * clustering index (if one is passed) are still locked in a mode that allows + * concurrent data changes. On exit, both tables and their indexes are closed, + * but locked in AccessExclusiveLock mode. + */ +static void +rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap, + Oid identIdx, TransactionId frozenXid, + MultiXactId cutoffMulti) +{ + LOCKMODE lockmode_old PG_USED_FOR_ASSERTS_ONLY; + List *ind_oids_new; + Oid old_table_oid = RelationGetRelid(OldHeap); + Oid new_table_oid = RelationGetRelid(NewHeap); + List *ind_oids_old = RelationGetIndexList(OldHeap); + ListCell *lc, + *lc2; + char relpersistence; + bool is_system_catalog; + Oid ident_idx_new; + XLogRecPtr wal_insert_ptr, + end_of_wal; + char dummy_rec_data = '\0'; + Relation *ind_refs, + *ind_refs_p; + int nind; + ChangeDest chgdst; + + /* Like in cluster_rel(). */ + lockmode_old = ShareUpdateExclusiveLock; + Assert(CheckRelationLockedByMe(OldHeap, lockmode_old, false)); + /* This is expected from the caller. */ + Assert(CheckRelationLockedByMe(NewHeap, AccessExclusiveLock, false)); + + /* + * Unlike the exclusive case, we build new indexes for the new relation + * rather than swapping the storage and reindexing the old relation. The + * point is that the index build can take some time, so we do it before we + * get AccessExclusiveLock on the old heap and therefore we cannot swap + * the heap storage yet. + * + * index_create() will lock the new indexes using AccessExclusiveLock - no + * need to change that. At the same time, we use ShareUpdateExclusiveLock + * to lock the existing indexes - that should be enough to prevent others + * from changing them while we're repacking the relation. The lock on + * table should prevent others from changing the index column list, but + * might not be enough for commands like ALTER INDEX ... SET ... (Those + * are not necessarily dangerous, but can make user confused if the + * changes they do get lost due to REPACK.) + */ + ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old); + + /* Find "identity index" on the new relation. */ + ident_idx_new = InvalidOid; + forboth(lc, ind_oids_old, lc2, ind_oids_new) + { + Oid ind_old = lfirst_oid(lc); + Oid ind_new = lfirst_oid(lc2); + + if (identIdx == ind_old) + { + ident_idx_new = ind_new; + break; + } + } + if (!OidIsValid(ident_idx_new)) + + /* + * Should not happen, given our lock on the old relation. + */ + ereport(ERROR, + (errmsg("identity index missing on the new relation"))); + + /* Gather information to apply concurrent changes. */ + chgdst.rel = NewHeap; + chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new, + &chgdst.ident_index); + chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap, + &chgdst.ident_key_nentries); + chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1; + + /* + * During testing, wait for another backend to perform concurrent data + * changes which we will process below. + */ + INJECTION_POINT("repack-concurrently-before-lock", NULL); + + /* + * Flush all WAL records inserted so far (possibly except for the last + * incomplete page, see GetInsertRecPtr), to minimize the amount of data + * we need to flush while holding exclusive lock on the source table. + */ + wal_insert_ptr = GetInsertRecPtr(); + XLogFlush(wal_insert_ptr); + end_of_wal = GetFlushRecPtr(NULL); + + /* + * Apply concurrent changes first time, to minimize the time we need to + * hold AccessExclusiveLock. (Quite some amount of WAL could have been + * written during the data copying and index creation.) + */ + process_concurrent_changes(end_of_wal, &chgdst, false); + + /* + * Acquire AccessExclusiveLock on the table, its TOAST relation (if there + * is one), all its indexes, so that we can swap the files. + */ + LockRelationOid(old_table_oid, AccessExclusiveLock); + + /* + * Lock all indexes now, not only the clustering one: all indexes need to + * have their files swapped. While doing that, store their relation + * references in an array, to handle predicate locks below. + */ + ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old)); + nind = 0; + foreach_oid(ind_oid, ind_oids_old) + { + Relation index; + + index = index_open(ind_oid, AccessExclusiveLock); + + /* + * TODO 1) Do we need to check if ALTER INDEX was executed since the + * new index was created in build_new_indexes()? 2) Specifically for + * the clustering index, should check_index_is_clusterable() be called + * here? (Not sure about the latter: ShareUpdateExclusiveLock on the + * table probably blocks all commands that affect the result of + * check_index_is_clusterable().) + */ + *ind_refs_p = index; + ind_refs_p++; + nind++; + } + + /* + * Lock the OldHeap's TOAST relation exclusively - again, the lock is + * needed to swap the files. + */ + if (OidIsValid(OldHeap->rd_rel->reltoastrelid)) + LockRelationOid(OldHeap->rd_rel->reltoastrelid, AccessExclusiveLock); + + /* + * Tuples and pages of the old heap will be gone, but the heap will stay. + */ + TransferPredicateLocksToHeapRelation(OldHeap); + /* The same for indexes. */ + for (int i = 0; i < nind; i++) + { + Relation index = ind_refs[i]; + + TransferPredicateLocksToHeapRelation(index); + + /* + * References to indexes on the old relation are not needed anymore, + * however locks stay till the end of the transaction. + */ + index_close(index, NoLock); + } + pfree(ind_refs); + + /* + * Flush anything we see in WAL, to make sure that all changes committed + * while we were waiting for the exclusive lock are available for + * decoding. This should not be necessary if all backends had + * synchronous_commit set, but we can't rely on this setting. + * + * Unfortunately, GetInsertRecPtr() may lag behind the actual insert + * position, and GetLastImportantRecPtr() points at the start of the last + * record rather than at the end. Thus the simplest way to determine the + * insert position is to insert a dummy record and use its LSN. + * + * XXX Consider using GetLastImportantRecPtr() and adding the size of the + * last record (plus the total size of all the page headers the record + * spans)? + */ + XLogBeginInsert(); + XLogRegisterData(&dummy_rec_data, 1); + wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP); + XLogFlush(wal_insert_ptr); + end_of_wal = GetFlushRecPtr(NULL); + + /* + * Apply the concurrent changes again. Indicate that the decoding worker + * won't be needed anymore. + */ + process_concurrent_changes(end_of_wal, &chgdst, true); + + /* Remember info about rel before closing OldHeap */ + relpersistence = OldHeap->rd_rel->relpersistence; + is_system_catalog = IsSystemRelation(OldHeap); + + pgstat_progress_update_param(PROGRESS_REPACK_PHASE, + PROGRESS_REPACK_PHASE_SWAP_REL_FILES); + + /* + * Even ShareUpdateExclusiveLock should have prevented others from + * creating / dropping indexes (even using the CONCURRENTLY option), so we + * do not need to check whether the lists match. + */ + forboth(lc, ind_oids_old, lc2, ind_oids_new) + { + Oid ind_old = lfirst_oid(lc); + Oid ind_new = lfirst_oid(lc2); + Oid mapped_tables[4]; + + /* Zero out possible results from swapped_relation_files */ + memset(mapped_tables, 0, sizeof(mapped_tables)); + + swap_relation_files(ind_old, ind_new, + (old_table_oid == RelationRelationId), + false, /* swap_toast_by_content */ + true, + InvalidTransactionId, + InvalidMultiXactId, + mapped_tables); + +#ifdef USE_ASSERT_CHECKING + + /* + * Concurrent processing is not supported for system relations, so + * there should be no mapped tables. + */ + for (int i = 0; i < 4; i++) + Assert(mapped_tables[i] == 0); +#endif + } + + /* The new indexes must be visible for deletion. */ + CommandCounterIncrement(); + + /* Close the old heap but keep lock until transaction commit. */ + table_close(OldHeap, NoLock); + /* Close the new heap. (We didn't have to open its indexes). */ + table_close(NewHeap, NoLock); + + /* Cleanup what we don't need anymore. (And close the identity index.) */ + pfree(chgdst.ident_key); + free_index_insert_state(chgdst.iistate); + + /* + * Swap the relations and their TOAST relations and TOAST indexes. This + * also drops the new relation and its indexes. + * + * (System catalogs are currently not supported.) + */ + Assert(!is_system_catalog); + finish_heap_swap(old_table_oid, new_table_oid, + is_system_catalog, + false, /* swap_toast_by_content */ + false, + true, + false, /* reindex */ + frozenXid, cutoffMulti, + relpersistence); +} + +/* + * Build indexes on NewHeap according to those on OldHeap. + * + * OldIndexes is the list of index OIDs on OldHeap. The contained indexes end + * up locked using ShareUpdateExclusiveLock. + * + * A list of OIDs of the corresponding indexes created on NewHeap is + * returned. The order of items does match, so we can use these arrays to swap + * index storage. + */ +static List * +build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes) +{ + List *result = NIL; + + pgstat_progress_update_param(PROGRESS_REPACK_PHASE, + PROGRESS_REPACK_PHASE_REBUILD_INDEX); + + foreach_oid(ind_oid, OldIndexes) + { + Oid ind_oid_new; + char *newName; + Relation ind; + + ind = index_open(ind_oid, ShareUpdateExclusiveLock); + + newName = ChooseRelationName(get_rel_name(ind_oid), + NULL, + "repacknew", + get_rel_namespace(ind->rd_index->indrelid), + false); + ind_oid_new = index_create_copy(NewHeap, ind_oid, + ind->rd_rel->reltablespace, newName, + false); + result = lappend_oid(result, ind_oid_new); + + index_close(ind, NoLock); + } + + return result; +} + +/* + * Try to start a background worker to perform logical decoding of data + * changes applied to relation while REPACK CONCURRENTLY is copying its + * contents to a new table. + */ +static void +start_decoding_worker(Oid relid) +{ + Size size; + dsm_segment *seg; + DecodingWorkerShared *shared; + shm_mq *mq; + shm_mq_handle *mqh; + BackgroundWorker bgw; + + /* Setup shared memory. */ + size = BUFFERALIGN(offsetof(DecodingWorkerShared, error_queue)) + + BUFFERALIGN(REPACK_ERROR_QUEUE_SIZE); + seg = dsm_create(size, 0); + shared = (DecodingWorkerShared *) dsm_segment_address(seg); + shared->lsn_upto = InvalidXLogRecPtr; + shared->done = false; + SharedFileSetInit(&shared->sfs, seg); + shared->last_exported = -1; + SpinLockInit(&shared->mutex); + shared->dbid = MyDatabaseId; + + /* + * This is the UserId set in cluster_rel(). Security context shouldn't be + * needed for decoding worker. + */ + shared->roleid = GetUserId(); + shared->relid = relid; + ConditionVariableInit(&shared->cv); + shared->backend_proc = MyProc; + shared->backend_pid = MyProcPid; + shared->backend_proc_number = MyProcNumber; + + mq = shm_mq_create((char *) BUFFERALIGN(shared->error_queue), + REPACK_ERROR_QUEUE_SIZE); + shm_mq_set_receiver(mq, MyProc); + mqh = shm_mq_attach(mq, seg, NULL); + + memset(&bgw, 0, sizeof(bgw)); + snprintf(bgw.bgw_name, BGW_MAXLEN, + "REPACK decoding worker for relation \"%s\"", + get_rel_name(relid)); + snprintf(bgw.bgw_type, BGW_MAXLEN, "REPACK decoding worker"); + bgw.bgw_flags = BGWORKER_SHMEM_ACCESS | + BGWORKER_BACKEND_DATABASE_CONNECTION; + bgw.bgw_start_time = BgWorkerStart_RecoveryFinished; + bgw.bgw_restart_time = BGW_NEVER_RESTART; + snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres"); + snprintf(bgw.bgw_function_name, BGW_MAXLEN, "RepackWorkerMain"); + bgw.bgw_main_arg = UInt32GetDatum(dsm_segment_handle(seg)); + bgw.bgw_notify_pid = MyProcPid; + + decoding_worker = palloc0_object(DecodingWorker); + if (!RegisterDynamicBackgroundWorker(&bgw, &decoding_worker->handle)) + ereport(ERROR, + (errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED), + errmsg("out of background worker slots"), + errhint("You might need to increase \"%s\".", "max_worker_processes"))); + + decoding_worker->seg = seg; + decoding_worker->error_mqh = mqh; + + /* + * The decoding setup must be done before the caller can have XID assigned + * for any reason, otherwise the worker might end up in a deadlock, + * waiting for the caller's transaction to end. Therefore wait here until + * the worker indicates that it has the logical decoding initialized. + */ + ConditionVariablePrepareToSleep(&shared->cv); + for (;;) + { + bool initialized; + + SpinLockAcquire(&shared->mutex); + initialized = shared->initialized; + SpinLockRelease(&shared->mutex); + + if (initialized) + break; + + ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT); + } + ConditionVariableCancelSleep(); +} + +/* + * Stop the decoding worker and cleanup the related resources. + * + * The worker stops on its own when it knows there is no more work to do, but + * we need to stop it explicitly at least on ERROR in the launching backend. + */ +static void +stop_decoding_worker(void) +{ + BgwHandleStatus status; + + /* Haven't reached the worker startup? */ + if (decoding_worker == NULL) + return; + + /* Could not register the worker? */ + if (decoding_worker->handle == NULL) + return; + + TerminateBackgroundWorker(decoding_worker->handle); + /* The worker should really exit before the REPACK command does. */ + HOLD_INTERRUPTS(); + status = WaitForBackgroundWorkerShutdown(decoding_worker->handle); + RESUME_INTERRUPTS(); + + if (status == BGWH_POSTMASTER_DIED) + ereport(FATAL, + (errcode(ERRCODE_ADMIN_SHUTDOWN), + errmsg("postmaster exited during REPACK command"))); + + shm_mq_detach(decoding_worker->error_mqh); + + /* + * If we could not cancel the current sleep due to ERROR, do that before + * we detach from the shared memory the condition variable is located in. + * If we did not, the bgworker ERROR handling code would try and fail + * badly. + */ + ConditionVariableCancelSleep(); + + dsm_detach(decoding_worker->seg); + pfree(decoding_worker); + decoding_worker = NULL; +} + +/* Is this process a REPACK worker? */ +static bool is_repack_worker = false; + +static pid_t backend_pid; +static ProcNumber backend_proc_number; + +/* + * See ParallelWorkerShutdown for details. + */ +static void +RepackWorkerShutdown(int code, Datum arg) +{ + SendProcSignal(backend_pid, + PROCSIG_REPACK_MESSAGE, + backend_proc_number); + + dsm_detach((dsm_segment *) DatumGetPointer(arg)); +} + +/* REPACK decoding worker entry point */ +void +RepackWorkerMain(Datum main_arg) +{ + dsm_segment *seg; + DecodingWorkerShared *shared; + shm_mq *mq; + shm_mq_handle *mqh; + + is_repack_worker = true; + + /* + * Override the default bgworker_die() with die() so we can use + * CHECK_FOR_INTERRUPTS(). + */ + pqsignal(SIGTERM, die); + BackgroundWorkerUnblockSignals(); + + seg = dsm_attach(DatumGetUInt32(main_arg)); + if (seg == NULL) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("could not map dynamic shared memory segment"))); + + shared = (DecodingWorkerShared *) dsm_segment_address(seg); + + /* Arrange to signal the leader if we exit. */ + backend_pid = shared->backend_pid; + backend_proc_number = shared->backend_proc_number; + before_shmem_exit(RepackWorkerShutdown, PointerGetDatum(seg)); + + /* + * Join locking group - see the comments around the call of + * start_decoding_worker(). + */ + if (!BecomeLockGroupMember(shared->backend_proc, backend_pid)) + /* The leader is not running anymore. */ + return; + + /* + * Setup a queue to send error messages to the backend that launched this + * worker. + */ + mq = (shm_mq *) (char *) BUFFERALIGN(shared->error_queue); + shm_mq_set_sender(mq, MyProc); + mqh = shm_mq_attach(mq, seg, NULL); + pq_redirect_to_shm_mq(seg, mqh); + pq_set_parallel_leader(shared->backend_pid, + shared->backend_proc_number); + + /* Connect to the database. */ + BackgroundWorkerInitializeConnectionByOid(shared->dbid, shared->roleid, 0); + + repack_worker_internal(seg); +} + +static void +repack_worker_internal(dsm_segment *seg) +{ + DecodingWorkerShared *shared; + LogicalDecodingContext *decoding_ctx; + SharedFileSet *sfs; + Snapshot snapshot; + + /* + * Transaction is needed to open relation, and it also provides us with a + * resource owner. + */ + StartTransactionCommand(); + + shared = (DecodingWorkerShared *) dsm_segment_address(seg); + + /* + * Not sure the spinlock is needed here - the backend should not change + * anything in the shared memory until we have serialized the snapshot. + */ + SpinLockAcquire(&shared->mutex); + Assert(XLogRecPtrIsInvalid(shared->lsn_upto)); + sfs = &shared->sfs; + SpinLockRelease(&shared->mutex); + + SharedFileSetAttach(sfs, seg); + + /* + * Prepare to capture the concurrent data changes ourselves. + */ + decoding_ctx = setup_logical_decoding(shared->relid); + + /* Announce that we're ready. */ + SpinLockAcquire(&shared->mutex); + shared->initialized = true; + SpinLockRelease(&shared->mutex); + ConditionVariableSignal(&shared->cv); + + /* Build the initial snapshot and export it. */ + snapshot = SnapBuildInitialSnapshot(decoding_ctx->snapshot_builder, true); + export_initial_snapshot(snapshot, shared); + + /* + * Only historic snapshots should be used now. Do not let us restrict the + * progress of xmin horizon. + */ + InvalidateCatalogSnapshot(); + + while (!decode_concurrent_changes(decoding_ctx, shared)) + ; + + /* Cleanup. */ + cleanup_logical_decoding(decoding_ctx); + CommitTransactionCommand(); +} + +/* + * Make snapshot available to the backend that launched the decoding worker. + */ +static void +export_initial_snapshot(Snapshot snapshot, DecodingWorkerShared *shared) +{ + char fname[MAXPGPATH]; + BufFile *file; + Size snap_size; + char *snap_space; + + snap_size = EstimateSnapshotSpace(snapshot); + snap_space = (char *) palloc(snap_size); + SerializeSnapshot(snapshot, snap_space); + FreeSnapshot(snapshot); + + DecodingWorkerFileName(fname, shared->relid, shared->last_exported + 1); + file = BufFileCreateFileSet(&shared->sfs.fs, fname); + /* To make restoration easier, write the snapshot size first. */ + BufFileWrite(file, &snap_size, sizeof(snap_size)); + BufFileWrite(file, snap_space, snap_size); + pfree(snap_space); + BufFileClose(file); + + /* Increase the counter to tell the backend that the file is available. */ + SpinLockAcquire(&shared->mutex); + shared->last_exported++; + SpinLockRelease(&shared->mutex); + ConditionVariableSignal(&shared->cv); +} + +/* + * Get the initial snapshot from the decoding worker. + */ +static Snapshot +get_initial_snapshot(DecodingWorker *worker) +{ + DecodingWorkerShared *shared; + char fname[MAXPGPATH]; + BufFile *file; + Size snap_size; + char *snap_space; + Snapshot snapshot; + + shared = (DecodingWorkerShared *) dsm_segment_address(worker->seg); + + /* + * The worker needs to initialize the logical decoding, which usually + * takes some time. Therefore it makes sense to prepare for the sleep + * first. + */ + ConditionVariablePrepareToSleep(&shared->cv); + for (;;) + { + int last_exported; + + SpinLockAcquire(&shared->mutex); + last_exported = shared->last_exported; + SpinLockRelease(&shared->mutex); + + /* + * Has the worker exported the file we are waiting for? + */ + if (last_exported == WORKER_FILE_SNAPSHOT) + break; + + ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT); + } + ConditionVariableCancelSleep(); + + /* Read the snapshot from a file. */ + DecodingWorkerFileName(fname, shared->relid, WORKER_FILE_SNAPSHOT); + file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false); + BufFileReadExact(file, &snap_size, sizeof(snap_size)); + snap_space = (char *) palloc(snap_size); + BufFileReadExact(file, snap_space, snap_size); + BufFileClose(file); + + /* Restore it. */ + snapshot = RestoreSnapshot(snap_space); + pfree(snap_space); + + return snapshot; +} + +bool +IsRepackWorker(void) +{ + return is_repack_worker; +} + +/* + * Handle receipt of an interrupt indicating a repack worker message. + * + * Note: this is called within a signal handler! All we can do is set + * a flag that will cause the next CHECK_FOR_INTERRUPTS() to invoke + * ProcessRepackMessages(). + */ +void +HandleRepackMessageInterrupt(void) +{ + InterruptPending = true; + RepackMessagePending = true; + SetLatch(MyLatch); +} + +/* + * Process any queued protocol messages received from parallel workers. + */ +void +ProcessRepackMessages(void) +{ + MemoryContext oldcontext; + + static MemoryContext hpm_context = NULL; + + /* + * Nothing to do if we haven't launched the worker yet or have already + * terminated it. + */ + if (decoding_worker == NULL) + return; + + /* + * This is invoked from ProcessInterrupts(), and since some of the + * functions it calls contain CHECK_FOR_INTERRUPTS(), there is a potential + * for recursive calls if more signals are received while this runs. It's + * unclear that recursive entry would be safe, and it doesn't seem useful + * even if it is safe, so let's block interrupts until done. + */ + HOLD_INTERRUPTS(); + + /* + * Moreover, CurrentMemoryContext might be pointing almost anywhere. We + * don't want to risk leaking data into long-lived contexts, so let's do + * our work here in a private context that we can reset on each use. + */ + if (hpm_context == NULL) /* first time through? */ + hpm_context = AllocSetContextCreate(TopMemoryContext, + "ProcessRepackMessages", + ALLOCSET_DEFAULT_SIZES); + else + MemoryContextReset(hpm_context); + + oldcontext = MemoryContextSwitchTo(hpm_context); + + /* OK to process messages. Reset the flag saying there are more to do. */ + RepackMessagePending = false; + + /* + * Read as many messages as we can from each worker, but stop when no more + * messages can be read from the worker without blocking. + */ + while (true) + { + shm_mq_result res; + Size nbytes; + void *data; + + res = shm_mq_receive(decoding_worker->error_mqh, &nbytes, + &data, true); + if (res == SHM_MQ_WOULD_BLOCK) + break; + else if (res == SHM_MQ_SUCCESS) + { + StringInfoData msg; + + initStringInfo(&msg); + appendBinaryStringInfo(&msg, data, nbytes); + ProcessRepackMessage(&msg); + pfree(msg.data); + } + else + { + /* + * The decoding worker is special in that it exits as soon as it + * has its work done. Thus the DETACHED result code is fine. + */ + Assert(res == SHM_MQ_DETACHED); + + break; + } + } + + MemoryContextSwitchTo(oldcontext); + + /* Might as well clear the context on our way out */ + MemoryContextReset(hpm_context); + + RESUME_INTERRUPTS(); +} + +/* + * Process a single protocol message received from a single parallel worker. + */ +static void +ProcessRepackMessage(StringInfo msg) +{ + char msgtype; + + msgtype = pq_getmsgbyte(msg); + + switch (msgtype) + { + case PqMsg_ErrorResponse: + case PqMsg_NoticeResponse: + { + ErrorData edata; + + /* Parse ErrorResponse or NoticeResponse. */ + pq_parse_errornotice(msg, &edata); + + /* Death of a worker isn't enough justification for suicide. */ + edata.elevel = Min(edata.elevel, ERROR); + + /* + * If desired, add a context line to show that this is a + * message propagated from a parallel worker. Otherwise, it + * can sometimes be confusing to understand what actually + * happened. + */ + if (edata.context) + edata.context = psprintf("%s\n%s", edata.context, + _("decoding worker")); + else + edata.context = pstrdup(_("decoding worker")); + + /* Rethrow error or print notice. */ + ThrowErrorData(&edata); + + break; + } + + default: + { + elog(ERROR, "unrecognized message type received from decoding worker: %c (message length %d bytes)", + msgtype, msg->len); + } + } +} diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 81a55a33ef2..539969d6eef 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -893,6 +893,7 @@ static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence) { finish_heap_swap(matviewOid, OIDNewHeap, false, false, true, true, + true, /* reindex */ RecentXmin, ReadNextMultiXactId(), relpersistence); } diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 85242dcc245..895e6992b89 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -6035,6 +6035,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, finish_heap_swap(tab->relid, OIDNewHeap, false, false, true, !OidIsValid(tab->newTableSpace), + true, /* reindex */ RecentXmin, ReadNextMultiXactId(), persistence); diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..201835f30a4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -127,7 +127,7 @@ static void vac_truncate_clog(TransactionId frozenXID, TransactionId lastSaneFrozenXid, MultiXactId lastSaneMinMulti); static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, - BufferAccessStrategy bstrategy); + BufferAccessStrategy bstrategy, bool isTopLevel); static double compute_parallel_delay(void); static VacOptValue get_vacoptval_from_boolean(DefElem *def); static bool vac_tid_reaped(ItemPointer itemptr, void *state); @@ -630,7 +630,8 @@ vacuum(List *relations, const VacuumParams params, BufferAccessStrategy bstrateg if (params.options & VACOPT_VACUUM) { - if (!vacuum_rel(vrel->oid, vrel->relation, params, bstrategy)) + if (!vacuum_rel(vrel->oid, vrel->relation, params, bstrategy, + isTopLevel)) continue; } @@ -2004,7 +2005,7 @@ vac_truncate_clog(TransactionId frozenXID, */ static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, - BufferAccessStrategy bstrategy) + BufferAccessStrategy bstrategy, bool isTopLevel) { LOCKMODE lmode; Relation rel; @@ -2295,7 +2296,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, /* VACUUM FULL is a variant of REPACK; see cluster.c */ cluster_rel(REPACK_COMMAND_VACUUMFULL, rel, InvalidOid, - &cluster_params); + &cluster_params, isTopLevel); /* cluster_rel closes the relation, but keeps lock */ rel = NULL; @@ -2338,7 +2339,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, toast_vacuum_params.options |= VACOPT_PROCESS_MAIN; toast_vacuum_params.toast_parent = relid; - vacuum_rel(toast_relid, NULL, toast_vacuum_params, bstrategy); + vacuum_rel(toast_relid, NULL, toast_vacuum_params, bstrategy, + isTopLevel); } /* diff --git a/src/backend/libpq/pqmq.c b/src/backend/libpq/pqmq.c index 22e5164adbf..1000b7bb06e 100644 --- a/src/backend/libpq/pqmq.c +++ b/src/backend/libpq/pqmq.c @@ -14,6 +14,7 @@ #include "postgres.h" #include "access/parallel.h" +#include "commands/cluster.h" #include "libpq/libpq.h" #include "libpq/pqformat.h" #include "libpq/pqmq.h" @@ -177,6 +178,10 @@ mq_putmessage(char msgtype, const char *s, size_t len) SendProcSignal(pq_mq_parallel_leader_pid, PROCSIG_PARALLEL_APPLY_MESSAGE, pq_mq_parallel_leader_proc_number); + else if (IsRepackWorker()) + SendProcSignal(pq_mq_parallel_leader_pid, + PROCSIG_REPACK_MESSAGE, + pq_mq_parallel_leader_proc_number); else { Assert(IsParallelWorker()); diff --git a/src/backend/meson.build b/src/backend/meson.build index 4f5292d8f88..5e3cfe2d6f8 100644 --- a/src/backend/meson.build +++ b/src/backend/meson.build @@ -219,5 +219,6 @@ pg_test_mod_args = pg_mod_args + { subdir('jit/llvm') subdir('replication/libpqwalreceiver') subdir('replication/pgoutput') +subdir('replication/pgoutput_repack') subdir('snowball') subdir('utils/mb/conversion_procs') diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c index 0104a86b9ec..c9ef6bed88f 100644 --- a/src/backend/postmaster/bgworker.c +++ b/src/backend/postmaster/bgworker.c @@ -13,6 +13,7 @@ #include "postgres.h" #include "access/parallel.h" +#include "commands/cluster.h" #include "libpq/pqsignal.h" #include "miscadmin.h" #include "pgstat.h" @@ -143,6 +144,10 @@ static const struct { .fn_name = "SequenceSyncWorkerMain", .fn_addr = SequenceSyncWorkerMain + }, + { + .fn_name = "RepackWorkerMain", + .fn_addr = RepackWorkerMain } }; diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index 21f03864a66..2595ff0e3a6 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -33,6 +33,7 @@ #include "access/xlogreader.h" #include "access/xlogrecord.h" #include "catalog/pg_control.h" +#include "commands/cluster.h" #include "replication/decode.h" #include "replication/logical.h" #include "replication/message.h" @@ -420,7 +421,8 @@ heap2_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) { case XLOG_HEAP2_MULTI_INSERT: if (SnapBuildProcessChange(builder, xid, buf->origptr) && - !ctx->fast_forward) + !ctx->fast_forward && + !change_useless_for_repack(buf)) DecodeMultiInsert(ctx, buf); break; case XLOG_HEAP2_NEW_CID: @@ -466,6 +468,15 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) TransactionId xid = XLogRecGetXid(buf->record); SnapBuild *builder = ctx->snapshot_builder; + /* + * XXX Should we return here if change_useless_for_repack() returns true, + * instead of calling the function below? Unlike the fast-forward case, we + * shouldn't need the base snapshot for the containing transaction until + * we receive a change that belongs to the table being REPACKed. Thus it + * should be fine to skip SnapBuildProcessChange(), and therefore + * reorderbuffer.c can create the transaction later. + */ + ReorderBufferProcessXid(ctx->reorder, xid, buf->origptr); /* @@ -483,7 +494,8 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) { case XLOG_HEAP_INSERT: if (SnapBuildProcessChange(builder, xid, buf->origptr) && - !ctx->fast_forward) + !ctx->fast_forward && + !change_useless_for_repack(buf)) DecodeInsert(ctx, buf); break; @@ -495,19 +507,22 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_HEAP_HOT_UPDATE: case XLOG_HEAP_UPDATE: if (SnapBuildProcessChange(builder, xid, buf->origptr) && - !ctx->fast_forward) + !ctx->fast_forward && + !change_useless_for_repack(buf)) DecodeUpdate(ctx, buf); break; case XLOG_HEAP_DELETE: if (SnapBuildProcessChange(builder, xid, buf->origptr) && - !ctx->fast_forward) + !ctx->fast_forward && + !change_useless_for_repack(buf)) DecodeDelete(ctx, buf); break; case XLOG_HEAP_TRUNCATE: if (SnapBuildProcessChange(builder, xid, buf->origptr) && - !ctx->fast_forward) + !ctx->fast_forward && + !change_useless_for_repack(buf)) DecodeTruncate(ctx, buf); break; @@ -523,7 +538,8 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_HEAP_CONFIRM: if (SnapBuildProcessChange(builder, xid, buf->origptr) && - !ctx->fast_forward) + !ctx->fast_forward && + !change_useless_for_repack(buf)) DecodeSpecConfirm(ctx, buf); break; @@ -1020,6 +1036,15 @@ DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) xlrec = (xl_heap_delete *) XLogRecGetData(r); + /* + * Ignore changes which are considered useless for logical decoding. + * Currently such changes are created by REPACK (CONCURRENTLY) when + * replays DELETE commands on the new table (which is not yet visible to + * other transactions). + */ + if (xlrec->flags & XLH_DELETE_NO_LOGICAL) + return; + /* only interested in our database */ XLogRecGetBlockTag(r, 0, &target_locator, NULL, NULL); if (target_locator.dbOid != ctx->slot->data.database) diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c index 603a2b94d05..7651b187418 100644 --- a/src/backend/replication/logical/logical.c +++ b/src/backend/replication/logical/logical.c @@ -194,7 +194,11 @@ StartupDecodingContext(List *output_plugin_options, ctx->slot = slot; - ctx->reader = XLogReaderAllocate(wal_segment_size, NULL, xl_routine, ctx); + /* + * TODO A separate patch for PG core, unless there's really a reason to + * pass ctx for private_data (May extensions expect ctx?). + */ + ctx->reader = XLogReaderAllocate(wal_segment_size, NULL, xl_routine, NULL); if (!ctx->reader) ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index 37f0c6028bd..9cf499ce7c6 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -440,7 +440,7 @@ SnapBuildBuildSnapshot(SnapBuild *builder) * for loading in different transaction. */ Snapshot -SnapBuildInitialSnapshot(SnapBuild *builder) +SnapBuildInitialSnapshot(SnapBuild *builder, bool repack) { Snapshot snap; TransactionId xid; @@ -448,7 +448,7 @@ SnapBuildInitialSnapshot(SnapBuild *builder) TransactionId *newxip; int newxcnt = 0; - Assert(XactIsoLevel == XACT_REPEATABLE_READ); + Assert(XactIsoLevel == XACT_REPEATABLE_READ || repack); Assert(builder->building_full_snapshot); /* don't allow older snapshots */ @@ -526,6 +526,11 @@ SnapBuildInitialSnapshot(SnapBuild *builder) snap->xcnt = newxcnt; snap->xip = newxip; + /* + * FreeSnapshot() is more appropriate for REPACK than counting references. + */ + snap->copied = repack; + return snap; } @@ -558,7 +563,7 @@ SnapBuildExportSnapshot(SnapBuild *builder) XactIsoLevel = XACT_REPEATABLE_READ; XactReadOnly = true; - snap = SnapBuildInitialSnapshot(builder); + snap = SnapBuildInitialSnapshot(builder, false); /* * now that we've built a plain snapshot, make it active and use the diff --git a/src/backend/replication/pgoutput_repack/Makefile b/src/backend/replication/pgoutput_repack/Makefile new file mode 100644 index 00000000000..4efeb713b70 --- /dev/null +++ b/src/backend/replication/pgoutput_repack/Makefile @@ -0,0 +1,32 @@ +#------------------------------------------------------------------------- +# +# Makefile-- +# Makefile for src/backend/replication/pgoutput_repack +# +# IDENTIFICATION +# src/backend/replication/pgoutput_repack +# +#------------------------------------------------------------------------- + +subdir = src/backend/replication/pgoutput_repack +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global + +OBJS = \ + $(WIN32RES) \ + pgoutput_repack.o +PGFILEDESC = "pgoutput_repack - logical replication output plugin for REPACK command" +NAME = pgoutput_repack + +all: all-shared-lib + +include $(top_srcdir)/src/Makefile.shlib + +install: all installdirs install-lib + +installdirs: installdirs-lib + +uninstall: uninstall-lib + +clean distclean: clean-lib + rm -f $(OBJS) diff --git a/src/backend/replication/pgoutput_repack/meson.build b/src/backend/replication/pgoutput_repack/meson.build new file mode 100644 index 00000000000..6a88c0fb08d --- /dev/null +++ b/src/backend/replication/pgoutput_repack/meson.build @@ -0,0 +1,18 @@ +# Copyright (c) 2022-2026, PostgreSQL Global Development Group + +pgoutput_repack_sources = files( + 'pgoutput_repack.c', +) + +if host_system == 'windows' + pgoutput_repack_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'pgoutput_repack', + '--FILEDESC', 'pgoutput_repack - logical replication output plugin for REPACK command',]) +endif + +pgoutput_repack = shared_module('pgoutput_repack', + pgoutput_repack_sources, + kwargs: pg_mod_args, +) + +backend_targets += pgoutput_repack diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c new file mode 100644 index 00000000000..d0c464a98d5 --- /dev/null +++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c @@ -0,0 +1,202 @@ +/*------------------------------------------------------------------------- + * + * pgoutput_repack.c + * Logical Replication output plugin for REPACK command + * + * Copyright (c) 2012-2026, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/replication/pgoutput_repack/pgoutput_repack.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/heaptoast.h" +#include "commands/cluster.h" +#include "replication/snapbuild.h" +#include "utils/memutils.h" + +PG_MODULE_MAGIC; + +static void plugin_startup(LogicalDecodingContext *ctx, + OutputPluginOptions *opt, bool is_init); +static void plugin_shutdown(LogicalDecodingContext *ctx); +static void plugin_begin_txn(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn); +static void plugin_commit_txn(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, XLogRecPtr commit_lsn); +static void plugin_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, + Relation rel, ReorderBufferChange *change); +static void store_change(LogicalDecodingContext *ctx, + ConcurrentChangeKind kind, HeapTuple tuple); + +void +_PG_output_plugin_init(OutputPluginCallbacks *cb) +{ + cb->startup_cb = plugin_startup; + cb->begin_cb = plugin_begin_txn; + cb->change_cb = plugin_change; + cb->commit_cb = plugin_commit_txn; + cb->shutdown_cb = plugin_shutdown; +} + + +/* initialize this plugin */ +static void +plugin_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt, + bool is_init) +{ + ctx->output_plugin_private = NULL; + + /* Probably unnecessary, as we don't use the SQL interface ... */ + opt->output_type = OUTPUT_PLUGIN_BINARY_OUTPUT; + + if (ctx->output_plugin_options != NIL) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("This plugin does not expect any options"))); + } +} + +static void +plugin_shutdown(LogicalDecodingContext *ctx) +{ +} + +/* + * As we don't release the slot during processing of particular table, there's + * no room for SQL interface, even for debugging purposes. Therefore we need + * neither OutputPluginPrepareWrite() nor OutputPluginWrite() in the plugin + * callbacks. (Although we might want to write custom callbacks, this API + * seems to be unnecessarily generic for our purposes.) + */ + +/* BEGIN callback */ +static void +plugin_begin_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn) +{ +} + +/* COMMIT callback */ +static void +plugin_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, + XLogRecPtr commit_lsn) +{ +} + +/* + * Callback for individual changed tuples + */ +static void +plugin_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, + Relation relation, ReorderBufferChange *change) +{ + RepackDecodingState *dstate; + + dstate = (RepackDecodingState *) ctx->output_writer_private; + + /* Only interested in one particular relation. */ + if (relation->rd_id != dstate->relid) + return; + + /* Decode entry depending on its type */ + switch (change->action) + { + case REORDER_BUFFER_CHANGE_INSERT: + { + HeapTuple newtuple; + + newtuple = change->data.tp.newtuple; + + /* + * Identity checks in the main function should have made this + * impossible. + */ + if (newtuple == NULL) + elog(ERROR, "incomplete insert info."); + + store_change(ctx, CHANGE_INSERT, newtuple); + } + break; + case REORDER_BUFFER_CHANGE_UPDATE: + { + HeapTuple oldtuple, + newtuple; + + oldtuple = change->data.tp.oldtuple; + newtuple = change->data.tp.newtuple; + + if (newtuple == NULL) + elog(ERROR, "incomplete update info."); + + if (oldtuple != NULL) + store_change(ctx, CHANGE_UPDATE_OLD, oldtuple); + + store_change(ctx, CHANGE_UPDATE_NEW, newtuple); + } + break; + case REORDER_BUFFER_CHANGE_DELETE: + { + HeapTuple oldtuple; + + oldtuple = change->data.tp.oldtuple; + + if (oldtuple == NULL) + elog(ERROR, "incomplete delete info."); + + store_change(ctx, CHANGE_DELETE, oldtuple); + } + break; + default: + + /* + * Should not come here. This includes TRUNCATE of the table being + * processed. heap_decode() cannot check the file locator easily, + * but we assume that TRUNCATE uses AccessExclusiveLock on the + * table so it should not occur during REPACK (CONCURRENTLY). + */ + Assert(false); + break; + } +} + +/* Store concurrent data change. */ +static void +store_change(LogicalDecodingContext *ctx, ConcurrentChangeKind kind, + HeapTuple tuple) +{ + RepackDecodingState *dstate; + char kind_byte = (char) kind; + bool flattened = false; + + dstate = (RepackDecodingState *) ctx->output_writer_private; + + /* Store the change kind. */ + BufFileWrite(dstate->file, &kind_byte, 1); + + /* + * ReorderBufferCommit() stores the TOAST chunks in its private memory + * context and frees them after having called apply_change(). Therefore + * we need flat copy (including TOAST) that we eventually copy into the + * memory context which is available to decode_concurrent_changes(). + */ + if (HeapTupleHasExternal(tuple)) + { + /* + * toast_flatten_tuple_to_datum() might be more convenient but we + * don't want the decompression it does. + */ + tuple = toast_flatten_tuple(tuple, dstate->tupdesc); + flattened = true; + } + /* Store the tuple size ... */ + BufFileWrite(dstate->file, &tuple->t_len, sizeof(tuple->t_len)); + /* ... and the tuple itself. */ + BufFileWrite(dstate->file, tuple->t_data, tuple->t_len); + + /* Free the flat copy if created above. */ + if (flattened) + pfree(tuple); +} diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 79fc192b171..4a62c9f645d 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -1338,7 +1338,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd) { Snapshot snap; - snap = SnapBuildInitialSnapshot(ctx->snapshot_builder); + snap = SnapBuildInitialSnapshot(ctx->snapshot_builder, false); RestoreTransactionSnapshot(snap, MyProc); } diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c index 7e017c8d53b..dd980145ced 100644 --- a/src/backend/storage/ipc/procsignal.c +++ b/src/backend/storage/ipc/procsignal.c @@ -19,6 +19,7 @@ #include "access/parallel.h" #include "commands/async.h" +#include "commands/cluster.h" #include "miscadmin.h" #include "pgstat.h" #include "port/pg_bitutils.h" @@ -700,6 +701,9 @@ procsignal_sigusr1_handler(SIGNAL_ARGS) if (CheckProcSignal(PROCSIG_PARALLEL_APPLY_MESSAGE)) HandleParallelApplyMessageInterrupt(); + if (CheckProcSignal(PROCSIG_REPACK_MESSAGE)) + HandleRepackMessageInterrupt(); + if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT)) HandleRecoveryConflictInterrupt(); diff --git a/src/backend/storage/lmgr/generate-lwlocknames.pl b/src/backend/storage/lmgr/generate-lwlocknames.pl index b49007167b0..2e7f1054e62 100644 --- a/src/backend/storage/lmgr/generate-lwlocknames.pl +++ b/src/backend/storage/lmgr/generate-lwlocknames.pl @@ -162,7 +162,7 @@ while (<$lwlocklist>) die "$wait_event_lwlocks[$lwlock_count] defined in wait_event_names.txt but " - . " missing from lwlocklist.h" + . "missing from lwlocklist.h" if $lwlock_count < scalar @wait_event_lwlocks; die diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index d01a09dd0c4..9711abaf2eb 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -36,6 +36,7 @@ #include "access/xact.h" #include "catalog/pg_type.h" #include "commands/async.h" +#include "commands/cluster.h" #include "commands/event_trigger.h" #include "commands/explain_state.h" #include "commands/prepare.h" @@ -3575,6 +3576,9 @@ ProcessInterrupts(void) if (ParallelApplyMessagePending) ProcessParallelApplyMessages(); + + if (RepackMessagePending) + ProcessRepackMessages(); } /* diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt index 4aa864fe3c3..b00bd794759 100644 --- a/src/backend/utils/activity/wait_event_names.txt +++ b/src/backend/utils/activity/wait_event_names.txt @@ -154,6 +154,7 @@ RECOVERY_CONFLICT_SNAPSHOT "Waiting for recovery conflict resolution for a vacuu RECOVERY_CONFLICT_TABLESPACE "Waiting for recovery conflict resolution for dropping a tablespace." RECOVERY_END_COMMAND "Waiting for <xref linkend="guc-recovery-end-command"/> to complete." RECOVERY_PAUSE "Waiting for recovery to be resumed." +REPACK_WORKER_EXPORT "Waiting for decoding worker to export a new output file." REPLICATION_ORIGIN_DROP "Waiting for a replication origin to become inactive so it can be dropped." REPLICATION_SLOT_DROP "Waiting for a replication slot to become inactive so it can be dropped." RESTORE_COMMAND "Waiting for <xref linkend="guc-restore-command"/> to complete." diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c index 2e6197f5f35..e0129df3e40 100644 --- a/src/backend/utils/time/snapmgr.c +++ b/src/backend/utils/time/snapmgr.c @@ -215,7 +215,6 @@ static List *exportedSnapshots = NIL; /* Prototypes for local functions */ static Snapshot CopySnapshot(Snapshot snapshot); static void UnregisterSnapshotNoOwner(Snapshot snapshot); -static void FreeSnapshot(Snapshot snapshot); static void SnapshotResetXmin(void); /* ResourceOwner callbacks to track snapshot references */ @@ -660,7 +659,7 @@ CopySnapshot(Snapshot snapshot) * FreeSnapshot * Free the memory associated with a snapshot. */ -static void +void FreeSnapshot(Snapshot snapshot) { Assert(snapshot->regd_count == 0); diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index 199fc64ddf5..add36423e3a 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -5153,8 +5153,8 @@ match_previous_words(int pattern_id, * one word, so the above test is correct. */ if (ends_with(prev_wd, '(') || ends_with(prev_wd, ',')) - COMPLETE_WITH("ANALYZE", "VERBOSE"); - else if (TailMatches("ANALYZE", "VERBOSE")) + COMPLETE_WITH("ANALYZE", "CONCURRENTLY", "VERBOSE"); + else if (TailMatches("ANALYZE", "CONCURRENTLY", "VERBOSE")) COMPLETE_WITH("ON", "OFF"); } diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index ad993c07311..02e64a1539c 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -374,14 +374,15 @@ extern void heap_multi_insert(Relation relation, TupleTableSlot **slots, BulkInsertState bistate); extern TM_Result heap_delete(Relation relation, const ItemPointerData *tid, CommandId cid, Snapshot crosscheck, bool wait, - TM_FailureData *tmfd, bool changingPart); + TM_FailureData *tmfd, bool changingPart, + bool wal_logical); extern void heap_finish_speculative(Relation relation, const ItemPointerData *tid); extern void heap_abort_speculative(Relation relation, const ItemPointerData *tid); extern TM_Result heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode, - TU_UpdateIndexes *update_indexes); + TU_UpdateIndexes *update_indexes, bool wal_logical); extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple, CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy, bool follow_updates, diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h index ce3566ba949..f1f5495556b 100644 --- a/src/include/access/heapam_xlog.h +++ b/src/include/access/heapam_xlog.h @@ -104,6 +104,8 @@ #define XLH_DELETE_CONTAINS_OLD_KEY (1<<2) #define XLH_DELETE_IS_SUPER (1<<3) #define XLH_DELETE_IS_PARTITION_MOVE (1<<4) +/* See heap_delete() */ +#define XLH_DELETE_NO_LOGICAL (1<<5) /* convenience macro for checking whether any form of old tuple was logged */ #define XLH_DELETE_CONTAINS_OLD \ diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index 06084752245..b9d5e5e13d9 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -629,6 +629,7 @@ typedef struct TableAmRoutine Relation OldIndex, bool use_sort, TransactionId OldestXmin, + Snapshot snapshot, TransactionId *xid_cutoff, MultiXactId *multi_cutoff, double *num_tuples, @@ -1657,6 +1658,8 @@ table_relation_copy_data(Relation rel, const RelFileLocator *newrlocator) * not needed for the relation's AM * - *xid_cutoff - ditto * - *multi_cutoff - ditto + * - snapshot - if != NULL, ignore data changes done by transactions that this + * (MVCC) snapshot considers still in-progress or in the future. * * Output parameters: * - *xid_cutoff - rel's new relfrozenxid value, may be invalid @@ -1669,6 +1672,7 @@ table_relation_copy_for_cluster(Relation OldTable, Relation NewTable, Relation OldIndex, bool use_sort, TransactionId OldestXmin, + Snapshot snapshot, TransactionId *xid_cutoff, MultiXactId *multi_cutoff, double *num_tuples, @@ -1677,6 +1681,7 @@ table_relation_copy_for_cluster(Relation OldTable, Relation NewTable, { OldTable->rd_tableam->relation_copy_for_cluster(OldTable, NewTable, OldIndex, use_sort, OldestXmin, + snapshot, xid_cutoff, multi_cutoff, num_tuples, tups_vacuumed, tups_recently_dead); diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h index 28741988478..1b05d5d418b 100644 --- a/src/include/commands/cluster.h +++ b/src/include/commands/cluster.h @@ -13,10 +13,17 @@ #ifndef CLUSTER_H #define CLUSTER_H +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" +#include "replication/decode.h" +#include "postmaster/bgworker.h" +#include "replication/logical.h" +#include "storage/buffile.h" #include "storage/lock.h" +#include "storage/shm_mq.h" #include "utils/relcache.h" +#include "utils/resowner.h" /* flag bits for ClusterParams->options */ @@ -25,6 +32,8 @@ #define CLUOPT_RECHECK_ISCLUSTERED 0x04 /* recheck relation state for * indisclustered */ #define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */ +#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */ + /* options for CLUSTER */ typedef struct ClusterParams @@ -33,10 +42,49 @@ typedef struct ClusterParams } ClusterParams; +/* + * The following definitions are used by REPACK CONCURRENTLY. + */ + +/* + * Stored as a single byte in the output file. + */ +typedef enum +{ + CHANGE_INSERT, + CHANGE_UPDATE_OLD, + CHANGE_UPDATE_NEW, + CHANGE_DELETE +} ConcurrentChangeKind; + +/* + * Logical decoding state. + * + * The output plugin uses it to store the data changes that it decodes from + * WAL while the table contents is being copied to a new storage. + */ +typedef struct RepackDecodingState +{ + /* The relation whose changes we're decoding. */ + Oid relid; + + /* Tuple descriptor of the relation being processed. */ + TupleDesc tupdesc; + + /* The current output file. */ + BufFile *file; +} RepackDecodingState; + +extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending; + +extern bool IsRepackWorker(void); +extern void HandleRepackMessageInterrupt(void); +extern void ProcessRepackMessages(void); + extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel); extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid, - ClusterParams *params); + ClusterParams *params, bool isTopLevel); extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid, LOCKMODE lockmode); extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); @@ -48,8 +96,13 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool swap_toast_by_content, bool check_constraints, bool is_internal, + bool reindex, TransactionId frozenXid, MultiXactId cutoffMulti, char newrelpersistence); +extern bool am_decoding_for_repack(void); +extern bool change_useless_for_repack(XLogRecordBuffer *buf); + +extern void RepackWorkerMain(Datum main_arg); #endif /* CLUSTER_H */ diff --git a/src/include/commands/progress.h b/src/include/commands/progress.h index 9c40772706c..b5b01c1bb6d 100644 --- a/src/include/commands/progress.h +++ b/src/include/commands/progress.h @@ -86,10 +86,12 @@ #define PROGRESS_REPACK_PHASE 1 #define PROGRESS_REPACK_INDEX_RELID 2 #define PROGRESS_REPACK_HEAP_TUPLES_SCANNED 3 -#define PROGRESS_REPACK_HEAP_TUPLES_WRITTEN 4 -#define PROGRESS_REPACK_TOTAL_HEAP_BLKS 5 -#define PROGRESS_REPACK_HEAP_BLKS_SCANNED 6 -#define PROGRESS_REPACK_INDEX_REBUILD_COUNT 7 +#define PROGRESS_REPACK_HEAP_TUPLES_INSERTED 4 +#define PROGRESS_REPACK_HEAP_TUPLES_UPDATED 5 +#define PROGRESS_REPACK_HEAP_TUPLES_DELETED 6 +#define PROGRESS_REPACK_TOTAL_HEAP_BLKS 7 +#define PROGRESS_REPACK_HEAP_BLKS_SCANNED 8 +#define PROGRESS_REPACK_INDEX_REBUILD_COUNT 9 /* * Phases of repack (as advertised via PROGRESS_REPACK_PHASE). @@ -98,9 +100,10 @@ #define PROGRESS_REPACK_PHASE_INDEX_SCAN_HEAP 2 #define PROGRESS_REPACK_PHASE_SORT_TUPLES 3 #define PROGRESS_REPACK_PHASE_WRITE_NEW_HEAP 4 -#define PROGRESS_REPACK_PHASE_SWAP_REL_FILES 5 -#define PROGRESS_REPACK_PHASE_REBUILD_INDEX 6 -#define PROGRESS_REPACK_PHASE_FINAL_CLEANUP 7 +#define PROGRESS_REPACK_PHASE_CATCH_UP 5 +#define PROGRESS_REPACK_PHASE_SWAP_REL_FILES 6 +#define PROGRESS_REPACK_PHASE_REBUILD_INDEX 7 +#define PROGRESS_REPACK_PHASE_FINAL_CLEANUP 8 /* Progress parameters for CREATE INDEX */ /* 3, 4 and 5 reserved for "waitfor" metrics */ diff --git a/src/include/replication/snapbuild.h b/src/include/replication/snapbuild.h index ccded021433..2b84f0058f0 100644 --- a/src/include/replication/snapbuild.h +++ b/src/include/replication/snapbuild.h @@ -72,7 +72,7 @@ extern void FreeSnapshotBuilder(SnapBuild *builder); extern void SnapBuildSnapDecRefcount(Snapshot snap); -extern Snapshot SnapBuildInitialSnapshot(SnapBuild *builder); +extern Snapshot SnapBuildInitialSnapshot(SnapBuild *builder, bool repack); extern const char *SnapBuildExportSnapshot(SnapBuild *builder); extern void SnapBuildClearExportedSnapshot(void); extern void SnapBuildResetExportedSnapshotState(void); diff --git a/src/include/storage/lockdefs.h b/src/include/storage/lockdefs.h index b73bb5618e6..3785b009808 100644 --- a/src/include/storage/lockdefs.h +++ b/src/include/storage/lockdefs.h @@ -36,8 +36,8 @@ typedef int LOCKMODE; #define AccessShareLock 1 /* SELECT */ #define RowShareLock 2 /* SELECT FOR UPDATE/FOR SHARE */ #define RowExclusiveLock 3 /* INSERT, UPDATE, DELETE */ -#define ShareUpdateExclusiveLock 4 /* VACUUM (non-FULL), ANALYZE, CREATE - * INDEX CONCURRENTLY */ +#define ShareUpdateExclusiveLock 4 /* VACUUM (non-exclusive), ANALYZE, CREATE + * INDEX CONCURRENTLY, REPACK CONCURRENTLY */ #define ShareLock 5 /* CREATE INDEX (WITHOUT CONCURRENTLY) */ #define ShareRowExclusiveLock 6 /* like EXCLUSIVE MODE, but allows ROW * SHARE */ diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h index 348fba53a93..a944ee0d211 100644 --- a/src/include/storage/procsignal.h +++ b/src/include/storage/procsignal.h @@ -36,6 +36,7 @@ typedef enum PROCSIG_BARRIER, /* global barrier interrupt */ PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts */ PROCSIG_PARALLEL_APPLY_MESSAGE, /* Message from parallel apply workers */ + PROCSIG_REPACK_MESSAGE, /* Message from repack worker */ PROCSIG_RECOVERY_CONFLICT, /* backend is blocking recovery, check * PGPROC->pendingRecoveryConflicts for the * reason */ diff --git a/src/include/utils/snapmgr.h b/src/include/utils/snapmgr.h index b8c01a291a1..0c144370308 100644 --- a/src/include/utils/snapmgr.h +++ b/src/include/utils/snapmgr.h @@ -63,6 +63,8 @@ extern Snapshot GetTransactionSnapshot(void); extern Snapshot GetLatestSnapshot(void); extern void SnapshotSetCommandId(CommandId curcid); +extern void FreeSnapshot(Snapshot snapshot); + extern Snapshot GetCatalogSnapshot(Oid relid); extern Snapshot GetNonHistoricCatalogSnapshot(Oid relid); extern void InvalidateCatalogSnapshot(void); diff --git a/src/test/modules/injection_points/Makefile b/src/test/modules/injection_points/Makefile index a41d781f8c9..2cd7d87c533 100644 --- a/src/test/modules/injection_points/Makefile +++ b/src/test/modules/injection_points/Makefile @@ -14,6 +14,8 @@ REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress ISOLATION = basic \ inplace \ + repack \ + repack_toast \ syscache-update-pruned \ heap_lock_update diff --git a/src/test/modules/injection_points/expected/repack.out b/src/test/modules/injection_points/expected/repack.out new file mode 100644 index 00000000000..b575e9052ee --- /dev/null +++ b/src/test/modules/injection_points/expected/repack.out @@ -0,0 +1,113 @@ +Parsed test spec with 2 sessions + +starting permutation: wait_before_lock change_existing change_new change_subxact1 change_subxact2 check2 wakeup_before_lock check1 +injection_points_attach +----------------------- + +(1 row) + +step wait_before_lock: + REPACK (CONCURRENTLY) repack_test USING INDEX repack_test_pkey; + <waiting ...> +step change_existing: + UPDATE repack_test SET i=10 where i=1; + UPDATE repack_test SET j=20 where i=2; + UPDATE repack_test SET i=30 where i=3; + UPDATE repack_test SET i=40 where i=30; + DELETE FROM repack_test WHERE i=4; + +step change_new: + INSERT INTO repack_test(i, j) VALUES (5, 5), (6, 6), (7, 7), (8, 8); + UPDATE repack_test SET i=50 where i=5; + UPDATE repack_test SET j=60 where i=6; + DELETE FROM repack_test WHERE i=7; + +step change_subxact1: + BEGIN; + INSERT INTO repack_test(i, j) VALUES (100, 100); + SAVEPOINT s1; + UPDATE repack_test SET i=101 where i=100; + SAVEPOINT s2; + UPDATE repack_test SET i=102 where i=101; + COMMIT; + +step change_subxact2: + BEGIN; + SAVEPOINT s1; + INSERT INTO repack_test(i, j) VALUES (110, 110); + ROLLBACK TO SAVEPOINT s1; + INSERT INTO repack_test(i, j) VALUES (110, 111); + COMMIT; + +step check2: + INSERT INTO relfilenodes(node) + SELECT relfilenode FROM pg_class WHERE relname='repack_test'; + + SELECT i, j FROM repack_test ORDER BY i, j; + + INSERT INTO data_s2(i, j) + SELECT i, j FROM repack_test; + + i| j +---+--- + 2| 20 + 6| 60 + 8| 8 + 10| 1 + 40| 3 + 50| 5 +102|100 +110|111 +(8 rows) + +step wakeup_before_lock: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step wait_before_lock: <... completed> +step check1: + INSERT INTO relfilenodes(node) + SELECT relfilenode FROM pg_class WHERE relname='repack_test'; + + SELECT count(DISTINCT node) FROM relfilenodes; + + SELECT i, j FROM repack_test ORDER BY i, j; + + INSERT INTO data_s1(i, j) + SELECT i, j FROM repack_test; + + SELECT count(*) + FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j) + WHERE d1.i ISNULL OR d2.i ISNULL; + +count +----- + 2 +(1 row) + + i| j +---+--- + 2| 20 + 6| 60 + 8| 8 + 10| 1 + 40| 3 + 50| 5 +102|100 +110|111 +(8 rows) + +count +----- + 0 +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/src/test/modules/injection_points/expected/repack_toast.out b/src/test/modules/injection_points/expected/repack_toast.out new file mode 100644 index 00000000000..4f866a74e32 --- /dev/null +++ b/src/test/modules/injection_points/expected/repack_toast.out @@ -0,0 +1,64 @@ +Parsed test spec with 2 sessions + +starting permutation: wait_before_lock change check2 wakeup_before_lock check1 +injection_points_attach +----------------------- + +(1 row) + +step wait_before_lock: + REPACK (CONCURRENTLY) repack_test; + <waiting ...> +step change: + UPDATE repack_test SET j=get_long_string() where i=2; + DELETE FROM repack_test WHERE i=3; + INSERT INTO repack_test(i, j) VALUES (4, get_long_string()); + +step check2: + INSERT INTO relfilenodes(node) + SELECT c2.relfilenode + FROM pg_class c1 JOIN pg_class c2 ON c2.oid = c1.oid OR c2.oid = c1.reltoastrelid + WHERE c1.relname='repack_test'; + + INSERT INTO data_s2(i, j) + SELECT i, j FROM repack_test; + +step wakeup_before_lock: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step wait_before_lock: <... completed> +step check1: + INSERT INTO relfilenodes(node) + SELECT c2.relfilenode + FROM pg_class c1 JOIN pg_class c2 ON c2.oid = c1.oid OR c2.oid = c1.reltoastrelid + WHERE c1.relname='repack_test'; + + SELECT count(DISTINCT node) FROM relfilenodes; + + INSERT INTO data_s1(i, j) + SELECT i, j FROM repack_test; + + SELECT count(*) + FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j) + WHERE d1.i ISNULL OR d2.i ISNULL; + +count +----- + 4 +(1 row) + +count +----- + 0 +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/src/test/modules/injection_points/meson.build b/src/test/modules/injection_points/meson.build index fcc85414515..a414abb924b 100644 --- a/src/test/modules/injection_points/meson.build +++ b/src/test/modules/injection_points/meson.build @@ -45,6 +45,8 @@ tests += { 'specs': [ 'basic', 'inplace', + 'repack', + 'repack_toast', 'syscache-update-pruned', 'heap_lock_update', ], diff --git a/src/test/modules/injection_points/specs/repack.spec b/src/test/modules/injection_points/specs/repack.spec new file mode 100644 index 00000000000..d727a9b056b --- /dev/null +++ b/src/test/modules/injection_points/specs/repack.spec @@ -0,0 +1,142 @@ +# REPACK (CONCURRENTLY) ... USING INDEX ...; +setup +{ + CREATE EXTENSION injection_points; + + CREATE TABLE repack_test(i int PRIMARY KEY, j int); + INSERT INTO repack_test(i, j) VALUES (1, 1), (2, 2), (3, 3), (4, 4); + + CREATE TABLE relfilenodes(node oid); + + CREATE TABLE data_s1(i int, j int); + CREATE TABLE data_s2(i int, j int); +} + +teardown +{ + DROP TABLE repack_test; + DROP EXTENSION injection_points; + + DROP TABLE relfilenodes; + DROP TABLE data_s1; + DROP TABLE data_s2; +} + +session s1 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +# Perform the initial load and wait for s2 to do some data changes. +step wait_before_lock +{ + REPACK (CONCURRENTLY) repack_test USING INDEX repack_test_pkey; +} +# Check the table from the perspective of s1. +# +# Besides the contents, we also check that relfilenode has changed. + +# Have each session write the contents into a table and use FULL JOIN to check +# if the outputs are identical. +step check1 +{ + INSERT INTO relfilenodes(node) + SELECT relfilenode FROM pg_class WHERE relname='repack_test'; + + SELECT count(DISTINCT node) FROM relfilenodes; + + SELECT i, j FROM repack_test ORDER BY i, j; + + INSERT INTO data_s1(i, j) + SELECT i, j FROM repack_test; + + SELECT count(*) + FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j) + WHERE d1.i ISNULL OR d2.i ISNULL; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); +} + +session s2 +# Change the existing data. UPDATE changes both key and non-key columns. Also +# update one row twice to test whether tuple version generated by this session +# can be found. +step change_existing +{ + UPDATE repack_test SET i=10 where i=1; + UPDATE repack_test SET j=20 where i=2; + UPDATE repack_test SET i=30 where i=3; + UPDATE repack_test SET i=40 where i=30; + DELETE FROM repack_test WHERE i=4; +} +# Insert new rows and UPDATE / DELETE some of them. Again, update both key and +# non-key column. +step change_new +{ + INSERT INTO repack_test(i, j) VALUES (5, 5), (6, 6), (7, 7), (8, 8); + UPDATE repack_test SET i=50 where i=5; + UPDATE repack_test SET j=60 where i=6; + DELETE FROM repack_test WHERE i=7; +} + +# When applying concurrent data changes, we should see the effects of an +# in-progress subtransaction. +# +# XXX Not sure this test is useful now - it was designed for the patch that +# preserves tuple visibility and which therefore modifies +# TransactionIdIsCurrentTransactionId(). +step change_subxact1 +{ + BEGIN; + INSERT INTO repack_test(i, j) VALUES (100, 100); + SAVEPOINT s1; + UPDATE repack_test SET i=101 where i=100; + SAVEPOINT s2; + UPDATE repack_test SET i=102 where i=101; + COMMIT; +} + +# When applying concurrent data changes, we should not see the effects of a +# rolled back subtransaction. +# +# XXX Is this test useful? See above. +step change_subxact2 +{ + BEGIN; + SAVEPOINT s1; + INSERT INTO repack_test(i, j) VALUES (110, 110); + ROLLBACK TO SAVEPOINT s1; + INSERT INTO repack_test(i, j) VALUES (110, 111); + COMMIT; +} + +# Check the table from the perspective of s2. +step check2 +{ + INSERT INTO relfilenodes(node) + SELECT relfilenode FROM pg_class WHERE relname='repack_test'; + + SELECT i, j FROM repack_test ORDER BY i, j; + + INSERT INTO data_s2(i, j) + SELECT i, j FROM repack_test; +} +step wakeup_before_lock +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +# Test if data changes introduced while one session is performing REPACK +# CONCURRENTLY find their way into the table. +permutation + wait_before_lock + change_existing + change_new + change_subxact1 + change_subxact2 + check2 + wakeup_before_lock + check1 diff --git a/src/test/modules/injection_points/specs/repack_toast.spec b/src/test/modules/injection_points/specs/repack_toast.spec new file mode 100644 index 00000000000..b48abf21450 --- /dev/null +++ b/src/test/modules/injection_points/specs/repack_toast.spec @@ -0,0 +1,105 @@ +# REPACK (CONCURRENTLY); +# +# Test handling of TOAST. At the same time, no tuplesort. +setup +{ + CREATE EXTENSION injection_points; + + -- Return a string that needs to be TOASTed. + CREATE FUNCTION get_long_string() + RETURNS text + LANGUAGE sql as $$ + SELECT string_agg(chr(65 + trunc(25 * random())::int), '') + FROM generate_series(1, 2048) s(x); + $$; + + CREATE TABLE repack_test(i int PRIMARY KEY, j text); + INSERT INTO repack_test(i, j) VALUES (1, get_long_string()), + (2, get_long_string()), (3, get_long_string()); + + CREATE TABLE relfilenodes(node oid); + + CREATE TABLE data_s1(i int, j text); + CREATE TABLE data_s2(i int, j text); +} + +teardown +{ + DROP TABLE repack_test; + DROP EXTENSION injection_points; + DROP FUNCTION get_long_string(); + + DROP TABLE relfilenodes; + DROP TABLE data_s1; + DROP TABLE data_s2; +} + +session s1 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +# Perform the initial load and wait for s2 to do some data changes. +step wait_before_lock +{ + REPACK (CONCURRENTLY) repack_test; +} +# Check the table from the perspective of s1. +# +# Besides the contents, we also check that relfilenode has changed. + +# Have each session write the contents into a table and use FULL JOIN to check +# if the outputs are identical. +step check1 +{ + INSERT INTO relfilenodes(node) + SELECT c2.relfilenode + FROM pg_class c1 JOIN pg_class c2 ON c2.oid = c1.oid OR c2.oid = c1.reltoastrelid + WHERE c1.relname='repack_test'; + + SELECT count(DISTINCT node) FROM relfilenodes; + + INSERT INTO data_s1(i, j) + SELECT i, j FROM repack_test; + + SELECT count(*) + FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j) + WHERE d1.i ISNULL OR d2.i ISNULL; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); +} + +session s2 +step change +{ + UPDATE repack_test SET j=get_long_string() where i=2; + DELETE FROM repack_test WHERE i=3; + INSERT INTO repack_test(i, j) VALUES (4, get_long_string()); +} +# Check the table from the perspective of s2. +step check2 +{ + INSERT INTO relfilenodes(node) + SELECT c2.relfilenode + FROM pg_class c1 JOIN pg_class c2 ON c2.oid = c1.oid OR c2.oid = c1.reltoastrelid + WHERE c1.relname='repack_test'; + + INSERT INTO data_s2(i, j) + SELECT i, j FROM repack_test; +} +step wakeup_before_lock +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +# Test if data changes introduced while one session is performing REPACK +# CONCURRENTLY find their way into the table. +permutation + wait_before_lock + change + check2 + wakeup_before_lock + check1 diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index f373ad704b6..e681b823c98 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -2014,7 +2014,7 @@ pg_stat_progress_cluster| SELECT pid, phase, repack_index_relid AS cluster_index_relid, heap_tuples_scanned, - heap_tuples_written, + (heap_tuples_inserted + heap_tuples_updated) AS heap_tuples_written, heap_blks_total, heap_blks_scanned, index_rebuild_count @@ -2094,17 +2094,20 @@ pg_stat_progress_repack| SELECT s.pid, WHEN 2 THEN 'index scanning heap'::text WHEN 3 THEN 'sorting tuples'::text WHEN 4 THEN 'writing new heap'::text - WHEN 5 THEN 'swapping relation files'::text - WHEN 6 THEN 'rebuilding index'::text - WHEN 7 THEN 'performing final cleanup'::text + WHEN 5 THEN 'catch-up'::text + WHEN 6 THEN 'swapping relation files'::text + WHEN 7 THEN 'rebuilding index'::text + WHEN 8 THEN 'performing final cleanup'::text ELSE NULL::text END AS phase, (s.param3)::oid AS repack_index_relid, s.param4 AS heap_tuples_scanned, - s.param5 AS heap_tuples_written, - s.param6 AS heap_blks_total, - s.param7 AS heap_blks_scanned, - s.param8 AS index_rebuild_count + s.param5 AS heap_tuples_inserted, + s.param6 AS heap_tuples_updated, + s.param7 AS heap_tuples_deleted, + s.param8 AS heap_blks_total, + s.param9 AS heap_blks_scanned, + s.param10 AS index_rebuild_count FROM (pg_stat_get_progress_info('REPACK'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20) LEFT JOIN pg_database d ON ((s.datid = d.oid))); pg_stat_progress_vacuum| SELECT s.pid, diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3da19d41413..1cea6db3559 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -421,6 +421,7 @@ CatCacheHeader CatalogId CatalogIdMapEntry CatalogIndexState +ChangeDest ChangeVarNodes_callback ChangeVarNodes_context ChannelName @@ -498,6 +499,7 @@ CompressFileHandle CompressionLocation CompressorState ComputeXidHorizonsResult +ConcurrentChangeKind ConditionVariable ConditionVariableMinimallyPadded ConditionalStack @@ -637,6 +639,8 @@ DeclareCursorStmt DecodedBkpBlock DecodedXLogRecord DecodingOutputState +DecodingWorker +DecodingWorkerShared DefElem DefElemAction DefaultACLInfo @@ -1282,6 +1286,7 @@ IndexElem IndexFetchHeapData IndexFetchTableData IndexInfo +IndexInsertState IndexList IndexOnlyScan IndexOnlyScanState @@ -2582,6 +2587,7 @@ ReorderBufferTupleCidKey ReorderBufferUpdateProgressTxnCB ReorderTuple RepackCommand +RepackDecodingState RepackStmt ReparameterizeForeignPathByChild_function ReplOriginId -- 2.47.3 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v41-0004-Serialize-decoded-tuples-without-flattening.patch ^ permalink raw reply [nested|flat] 22+ messages in thread
end of thread, other threads:[~2026-03-11 14:16 UTC | newest] Thread overview: 22+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2022-05-16 14:13 [PATCH] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2022-05-16 14:13 [PATCH] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2022-05-18 15:25 [PATCH] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2022-05-18 15:25 [PATCH] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2022-11-04 19:27 [PATCH 1/1] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2022-11-04 19:27 [PATCH 1/1] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2022-11-06 16:19 [PATCH 03e] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2022-11-06 16:19 [PATCH 03e] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2022-11-20 06:07 [PATCH 03f] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2022-11-20 06:07 [PATCH 03f] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2023-02-01 08:52 [PATCH v4] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2024-02-01 06:29 [PATCH v6] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2024-02-01 06:29 [PATCH v6] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2024-02-07 06:36 [PATCH v7] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2024-02-07 06:36 [PATCH v7] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2024-06-16 13:27 [PATCH v8 1/1] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2024-06-16 13:27 [PATCH v8 1/1] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2024-06-22 18:09 [PATCH v9 1/1] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2024-06-22 18:09 [PATCH v9 1/1] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2025-02-10 18:08 [PATCH v10 1/1] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2025-02-10 18:08 [PATCH v10 1/1] New options engine Nikolay Shaplov <dhyan@nataraj.su> 2026-03-11 14:16 [PATCH 3/5] Add CONCURRENTLY option to REPACK command. Antonin Houska <ah@cybertec.at>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox