Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1nWhoY-00040j-3O for pgsql-hackers@arkaria.postgresql.org; Tue, 22 Mar 2022 16:56:58 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1nWhoW-0007W2-NT for pgsql-hackers@arkaria.postgresql.org; Tue, 22 Mar 2022 16:56:56 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1nWhoW-0007Vs-6F for pgsql-hackers@lists.postgresql.org; Tue, 22 Mar 2022 16:56:56 +0000 Received: from ssl.nataraj.su ([88.198.198.57]) by magus.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1nWhoP-0005pX-Ei for pgsql-hackers@lists.postgresql.org; Tue, 22 Mar 2022 16:56:55 +0000 Received: from thinkpad-pgpro.localnet (unknown [91.203.188.92]) by ssl.nataraj.su (Postfix) with ESMTPSA id CBE8A9C414; Tue, 22 Mar 2022 16:56:47 +0000 (UTC) From: Nikolay Shaplov To: Andres Freund Cc: PostgreSQL Hackers , Alvaro Herrera , Michael Paquier , Amit Langote Subject: Re: [PATCH] New [relation] option engine Date: Tue, 22 Mar 2022 19:56:46 +0300 Message-ID: <4142565.V3TJ1GsxxI@thinkpad-pgpro> In-Reply-To: <20220322004610.7pqwd35up76xrpuv@alap3.anarazel.de> References: <3766675.7eaCOWfIcx@thinkpad-pgpro> <20220322004610.7pqwd35up76xrpuv@alap3.anarazel.de> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="nextPart9106738.F5RvnlrAqS" Content-Transfer-Encoding: 7Bit List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk This is a multi-part message in MIME format. --nextPart9106738.F5RvnlrAqS Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="UTF-8" =D0=92 =D0=BF=D0=B8=D1=81=D1=8C=D0=BC=D0=B5 =D0=BE=D1=82 =D0=B2=D1=82=D0=BE= =D1=80=D0=BD=D0=B8=D0=BA, 22 =D0=BC=D0=B0=D1=80=D1=82=D0=B0 2022 =D0=B3. 03= :46:10 MSK =D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0= =BB=D1=8C Andres Freund=20 =D0=BD=D0=B0=D0=BF=D0=B8=D1=81=D0=B0=D0=BB: > > I'd like to introduce a patch that reworks options processing. > This doesn't apply anymore: http://cfbot.cputube.org/patch_37_3536.log Thank you for sending this notice! I've updated the patch to include new changes, that have been made in postg= res=20 recently.=20 > Given that this patch has been submitted just to the last CF and that > there's been no action on it, I don't see this going into 15. Yes it is not to be added in 15 for sure. > Therefore I'd like to move it to the next CF? The only reason it can be kept in current CF, is to keep it in front of the= =20 eyes of potential reviewers for one more week. So it is up to you, if you=20 consider it does not worth it, you can move it now. No great harm would be= =20 done then.=20 =2D-=20 Nikolay Shaplov aka Nataraj =46uzzing Engineer at Postgres Professional Matrix IM: @dhyan:nataraj.su --nextPart9106738.F5RvnlrAqS Content-Disposition: attachment; filename="new_options_take_two_v01a.diff" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="UTF-8"; name="new_options_take_two_v01a.diff" 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..285f1a9c9c 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,28 @@ 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 +456,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 colNamvacuumcleanup = 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. @@ -1758,3 +1739,30 @@ 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..08d941c8b8 --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1370 @@ +/*------------------------------------------------------------------------- + * + * 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; + + 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 unprsed 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 valuess 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 coversion. + */ +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/relation.c b/src/backend/access/common/relation.c index 003663ab81..26d695d9b1 100644 --- a/src/backend/access/common/relation.c +++ b/src/backend/access/common/relation.c @@ -65,9 +65,13 @@ relation_open(Oid relationId, LOCKMODE lockmode) * If we didn't get the lock ourselves, assert that caller holds one, * except in bootstrap mode where no locks are used. */ - Assert(lockmode != NoLock || - IsBootstrapProcessingMode() || - CheckRelationLockedByMe(r, AccessShareLock, true)); + +// FIXME We need NoLock mode to get AM data when choosing Lock for +// attoptions is changed. See ProcessUtilitySlow problems comes from there +// This is a dirty hack, we need better solution for this case; +// Assert(lockmode != NoLock || +// IsBootstrapProcessingMode() || +// CheckRelationLockedByMe(r, AccessShareLock, true)); /* Make note that we've accessed a temporary relation */ if (RelationUsesLocalBuffers(r)) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 599e160ca6..760e532a66 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 -#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 */ -relopt_enum_elt_def StdRdOptIndexCleanupValues[] = +opt_enum_elt_def StdRdOptIndexCleanupValues[] = { {"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO}, {"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON}, @@ -489,17 +106,8 @@ relopt_enum_elt_def StdRdOptIndexCleanupValues[] = {(const char *) NULL} /* list terminator */ }; -/* values from GistOptBufferingMode */ -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 */ -relopt_enum_elt_def viewCheckOptValues[] = +opt_enum_elt_def viewCheckOptValues[] = { /* no value for NOT_SET */ {"local", VIEW_OPTION_CHECK_OPTION_LOCAL}, @@ -507,225 +115,9 @@ 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,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); } /* @@ -917,454 +163,60 @@ add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_ * '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; -} - - -/* - * 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; +add_local_int_reloption(local_relopts *relopts, const char *name, + const char *desc, int default_val, int min_val, + int max_val, int offset) +{ + optionsSpecSetAddInt(relopts->spec_set, name, desc, NoLock, offset, NULL, + default_val, min_val, max_val); } - /* - * Convert the text-array format of reloptions into a List of DefElem. - * This is the inverse of transformRelOptions(). + * add_local_real_reloption + * Add a new local float reloption + * + * 'offset' is offset of double-typed field. */ -List * -untransformRelOptions(Datum options) +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) { - 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, TYPALIGN_INT, - &optiondatums, NULL, &noptions); + 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,708 +253,478 @@ 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) -{ - 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++; - } - } +static void +toast_options_postprocess(void *data, bool validate) +{ + StdRdOptions * toast_options = (StdRdOptions *) data; + toast_options->fillfactor = 100; + toast_options->autovacuum.analyze_threshold = -1; + toast_options->autovacuum.analyze_scale_factor = -1; +} + + +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); } - /* Done if no options */ - if (PointerIsValid(DatumGetPointer(options))) - parseRelOptionsInternal(options, validate, reloptions, numoptions); + optionsSpecSetAddEnum(stdrd_relopt_spec_set, "vacuum_index_cleanup", + "Controls index vacuuming and index cleanup", + ShareUpdateExclusiveLock, + offsetof(StdRdOptions, vacuum_index_cleanup), + NULL, StdRdOptIndexCleanupValues, + STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO, + gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")); + + 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); + } - *numrelopts = numoptions; - return reloptions; + return stdrd_relopt_spec_set; } -/* Parse local unregistered options. */ -static relopt_value * -parseLocalRelOptions(local_relopts *relopts, Datum options, bool validate) + +static options_spec_set *heap_relopt_spec_set = NULL; + +options_spec_set * +get_heap_relopt_spec_set(void) { - int nopts = list_length(relopts->options); - relopt_value *values = palloc(sizeof(*values) * nopts); - ListCell *lc; - int i = 0; + 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; +} - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); +static options_spec_set *toast_relopt_spec_set = NULL; - values[i].gen = opt->option; - values[i].isset = false; +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); + return toast_relopt_spec_set; +} - i++; - } +static options_spec_set *partitioned_relopt_spec_set = NULL; - if (options != (Datum) 0) - parseRelOptionsInternal(options, validate, values, nopts); +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 values; + return partitioned_relopt_spec_set; } /* - * Subroutine for parseRelOptions, to parse and validate a single option's - * value + * Parse local options, allocate a bytea struct that's of the specified + * 'base_size' plus any extra space that's needed for string variables, + * fill its option's fields located at the given offsets and return it. */ -static void -parse_one_reloption(relopt_value *option, char *text_str, int text_len, - bool validate) +void * +build_local_reloptions(local_relopts *relopts, Datum options, bool validate) { - char *value; - int value_len; - bool parsed; - bool nofree = false; + void *opts; + ListCell *lc; + List *values; - if (option->isset && validate) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("parameter \"%s\" specified more than once", - option->gen->name))); + values = optionsTextArrayToRawValues(options); + values = optionsParseRawValues(values, relopts->spec_set, validate); + opts = optionsValuesToBytea(values, relopts->spec_set); - 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'; + /* + * Kind of ugly conversion here for backward compatibility. + * Would be removed while moving opclass options to options.c API + */ - switch (option->gen->type) + if (validate && relopts->validators) { - 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); -} - -/* - * 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; + int val_count = 0; + int i; + option_value *val_array; - for (i = 0; i < numoptions; i++) - { - relopt_value *optval = &options[i]; + foreach(lc, values) + val_count++; + val_array = palloc(sizeof(option_value)*val_count); - if (optval->gen->type == RELOPT_TYPE_STRING) + i=0; + foreach(lc, values) { - 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; + option_value *val = lfirst(lc); + memcpy(&(val_array[i]), val, sizeof(option_value)); + i++; } - } - - 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) -{ - int i; - int offset = basesize; - for (i = 0; i < numoptions; i++) - { - int j; - bool found = false; + foreach(lc, relopts->validators) + ((relopts_validator) lfirst(lc)) (opts, val_array, val_count); - 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); + pfree(val_array); } - SET_VARSIZE(rdopts, offset); -} - + return opts; -/* - * Option parser for anything that uses StdRdOptions. - */ -bytea * -default_reloptions(Datum reloptions, bool validate, relopt_kind kind) -{ - 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)); } /* - * 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. + * get_view_relopt_spec_set + * Returns an options catalog for view relation. */ -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 *view_relopt_spec_set = NULL; + +options_spec_set * +get_view_relopt_spec_set(void) { - int numoptions; - relopt_value *options; - void *rdopts; + if (view_relopt_spec_set) + return view_relopt_spec_set; - /* parse options specific to given relation option kind */ - options = parseRelOptions(reloptions, validate, kind, &numoptions); - Assert(numoptions <= num_relopt_elems); + view_relopt_spec_set = allocateOptionsSpecSet(NULL, + sizeof(ViewOptions), false, 3); - /* if none set, we're done */ - if (numoptions == 0) - { - Assert(options == NULL); - return NULL; - } + optionsSpecSetAddBool(view_relopt_spec_set, "security_barrier", + "View acts as a row security barrier", + AccessExclusiveLock, + offsetof(ViewOptions, security_barrier), NULL, false); - /* 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); + 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); - pfree(options); + 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 rdopts; + return view_relopt_spec_set; } /* - * Parse local options, allocate a bytea struct that's of the specified - * 'base_size' plus any extra space that's needed for string variables, - * fill its option's fields located at the given offsets and return it. + * get_attribute_options_spec_set + * Returns an options spec set for heap attributes */ -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; - - foreach(lc, relopts->options) - { - local_relopt *opt = lfirst(lc); - - elems[i].optname = opt->option->name; - elems[i].opttype = opt->option->type; - elems[i].offset = opt->offset; +static options_spec_set *attribute_options_spec_set = NULL; - i++; - } +options_spec_set * +get_attribute_options_spec_set(void) +{ + if (attribute_options_spec_set) + return attribute_options_spec_set; - vals = parseLocalRelOptions(relopts, options, validate); - opts = allocateReloptStruct(relopts->relopt_struct_size, vals, noptions); - fillRelOptions(opts, relopts->relopt_struct_size, vals, noptions, validate, - elems, noptions); + attribute_options_spec_set = allocateOptionsSpecSet(NULL, + sizeof(AttributeOpts), false, 2); - foreach(lc, relopts->validators) - ((relopts_validator) lfirst(lc)) (opts, vals, noptions); + 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); - if (elems) - pfree(elems); + 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 opts; + return attribute_options_spec_set; } -/* - * 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 - */ -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)); -} + * get_tablespace_options_spec_set + * Returns an options spec set for tablespaces +*/ +static options_spec_set *tablespace_options_spec_set = NULL; -/* - * Parse options for heaps, views and toast tables. - */ -bytea * -heap_reloptions(char relkind, Datum reloptions, bool validate) +options_spec_set * +get_tablespace_options_spec_set(void) { - StdRdOptions *rdopts; - - switch (relkind) + if (!tablespace_options_spec_set) { - 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; - } -} + 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); -/* - * 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); - - /* Assume function is strict */ - if (!PointerIsValid(DatumGetPointer(reloptions))) - return NULL; + 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); - return amoptions(reloptions, validate); + 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; } /* - * Option parser for attribute reloptions + * 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. */ -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)} - }; - - 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)); -} /* * Determine the required LOCKMODE from an option list. @@ -2110,33 +733,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..78c3f8976e 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 */ +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 a259a301fa..31c9c77941 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 edb6fa968f..a62fbcba93 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 c9b4964c1e..9930172037 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 84164748b3..080e25e9fa 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include #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,25 +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 1ae7492216..1cec98c3a9 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,23 +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). @@ -1349,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 9abbb6b555..53885088ab 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 cd30f15eba..3ed5370037 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; @@ -837,7 +838,7 @@ DefineIndex(Oid relationId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amreloptspecsetfn = amRoutine->amreloptspecset; pfree(amRoutine); ReleaseSysCache(tuple); @@ -851,10 +852,17 @@ 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); /* * Prepare arguments for index_create, primarily an IndexInfo structure. @@ -1987,8 +1995,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 80faae985e..bfced0bc37 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" @@ -671,7 +672,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; @@ -819,19 +819,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); + { + /* 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) @@ -4103,7 +4120,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); @@ -4145,7 +4162,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. @@ -4366,9 +4383,13 @@ 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, NoLock); // FIXME I am not sure how wise it is + cmd_lockmode = AlterTableGetRelOptionsLockLevel(rel, + castNode(List, cmd->def)); + relation_close(rel,NoLock); + break; + } case AT_AttachPartition: cmd_lockmode = ShareUpdateExclusiveLock; break; @@ -8131,12 +8152,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)); @@ -14115,12 +14137,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 */ @@ -14140,38 +14163,58 @@ 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)))); + break; + } + 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), @@ -14181,11 +14224,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; @@ -14260,20 +14307,22 @@ 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 40514ab550..5ddc37033a 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -363,10 +363,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 cd946c7692..bb1a4c4996 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1758,7 +1758,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; @@ -1822,7 +1822,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 3780c6e812..063b6b4344 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1162,6 +1162,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; @@ -1185,15 +1186,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); @@ -1302,9 +1304,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, NoLock); // FIXME! + 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..5ad32a8773 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 fbd11883e1..21b7e89b5f 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -460,7 +460,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; - amoptions_function amoptsfn; + amreloptspecset_function amoptspecsetfn; relation->rd_options = NULL; @@ -475,11 +475,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; @@ -490,7 +490,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..95dac315f0 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/include/access/amapi.h b/src/include/access/amapi.h index a382551a98..833a5259ee 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..e25ed4269b 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 cd7b2a53d8..58468249de 100644 --- a/src/include/access/hash.h +++ b/src/include/access/hash.h @@ -378,7 +378,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, @@ -470,6 +469,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 9fec6fb1a8..7d3e2a600c 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1252,7 +1252,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..3600d41e4d --- /dev/null +++ b/src/include/access/options.h @@ -0,0 +1,261 @@ +/*------------------------------------------------------------------------- + * + * 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..8514399e11 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,39 @@ #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 */ + List *validators; /* list of relopts_validator callbacks */ + 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 +66,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 +75,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..bfb0da3c4a 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[] = +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 -- --nextPart9106738.F5RvnlrAqS--