Re: Suggestion: Unified options API. Need help from core team - Mailing list pgsql-hackers

From Bruce Momjian
Subject Re: Suggestion: Unified options API. Need help from core team
Date
Msg-id 20211026142532.GB15874@momjian.us
Whole thread Raw
In response to Suggestion: Unified options API. Need help from core team  (Nikolay Shaplov <dhyan@nataraj.su>)
Responses Re: Suggestion: Unified options API. Need help from core team  (Nikolay Shaplov <dhyan@nataraj.su>)
List pgsql-hackers
Uh, the core team does not get involved in development issues, unless
there is a issue that clearly cannot be resolved by discussion on the
hackers list.

---------------------------------------------------------------------------

On Mon, Oct 18, 2021 at 04:24:23PM +0300, Nikolay Shaplov wrote:
> Hi!
> 
> I am still hoping to finish my work on reloptions I've started some years ago.
> 
> I've renewed my patch and I think I need help from core team to finish it.
> 
> General idea of the patch: Now we have three ways to define options for 
> different objects, with more or less different code used for it. It wold be 
> better to have unified context independent API for processing options, instead.
> 
> Long story short:
> 
> There is Option Specification object, that has all information about single 
> option, how it should be parsed and validated.
> 
> There is Option Specification Set object, an array of Option Specs, that defines 
> all options available for certain object (am of some index for example).
> 
> When some object (relation, opclass,  etc) wants to have an options, it 
> creates an Option Spec Set for there options, and uses it for converting 
> options between different representations (to get is from SQL, to store it in 
> pg_class, to pass it to the core code as bytea etc)
> 
> For indexes Option Spec Set is available via Access Method API. 
> 
> For non-index relations all Option Spec Sets are left in reloption.c file, and 
> should be moved to heap AM later. (They are not in AM now so will not change 
> it now)
> 
> Main problem:
> 
> There are LockModes. LockModes for options is also stored in Option Spec Set. 
> For indexes Option Spec Sec is accessable via AM. So to get LockMode for 
> option of an index you need to have access for it's relation object (so you 
> can call proper AM method to fetch spec set). So you need "Relation rel" in 
> AlterTableGetRelOptionsLockLevel where Lock Level is determinated (src/
> backend/access/common/reloptions.c)
> AlterTableGetRelOptionsLockLevel is called from AlterTableGetLockLevel (src/
> backend/commands/tablecmds.c) so we need "Relation rel" there too.
> AlterTableGetLockLevel is called from AlterTableInternal (/src/backend/
> commands/tablecmds.c) There we have "Oid relid" so we can try to open relation 
> like this
> 
>                    Relation rel = relation_open(relid, NoLock);
>                    cmd_lockmode = AlterTableGetRelOptionsLockLevel(rel,
>                                                    castNode(List, cmd->def));
>                    relation_close(rel,NoLock);
>                    break;
> 
> but this will trigger the assertion 
> 
>    Assert(lockmode != NoLock ||
>           IsBootstrapProcessingMode() ||
>           CheckRelationLockedByMe(r, c, true));
> 
> in relation_open (b/src/backend/access/common/relation.c)
> 
> For now I've commented this assertion out. I've tried to open relation with 
> AccessShareLock but this caused one test to fail, and I am not sure this 
> solution is better.
> 
> What I have done here I consider a hack, so I need a help of core-team here to 
> do it in right way.
> 
> General problems:
> 
> I guess I need a coauthor, or supervisor from core team, to finish this patch. 
> The amount of code is big, and I guess there are parts that can be made more 
> in postgres way, then I did them. And I would need an advice there, and I 
> guess it would be better to do if before sending it to commitfest.
> 
> 
> Current patch status:
> 
> 1. It is Beta. Some minor issues and FIXMEs are not solved. Some code comments 
> needs revising, but in general it do what it is intended to do.
> 
> 2. This patch does not intend to change postgres behavior at all, all should 
> work as before, all changes are internal only.
> 
> The only exception is error message for unexciting option name 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"
> 
> New message is better I guess, though I can change it back if needed.
> 
> 3. I am doing my development in this blanch https://gitlab.com/dhyannataraj/
> postgres/-/tree/new_options_take_two I am making changes every day, so last 
> version will be available there
> 
> Would be glad to hear from coreteam before I finish with this patch and made it 
> ready for commit-fest.
> 
> 

> diff --git a/contrib/bloom/bloom.h b/contrib/bloom/bloom.h
> index a22a6df..8f2d5e7 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 754de00..54dad16 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,53 +34,13 @@
>  
>  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.
>   */
>  static BloomOptions *
> @@ -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,39 @@ 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), 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
> +                                             * forbidden */
> +                             0,
> +                             offsetof(BloomOptions, bloomLength),
> +                             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
> +                                             * forbidden */
> +                                 0,
> +                                 offsetof(BloomOptions, bitSize[i]),
> +                                 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 dae12a7..e79456d 100644
> --- a/contrib/bloom/expected/bloom.out
> +++ b/contrib/bloom/expected/bloom.out
> @@ -228,3 +228,6 @@ CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=0);
>  ERROR:  value 0 out of bounds for option "length"
>  CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (col1=0);
>  ERROR:  value 0 out of bounds for option "col1"
> +-- check post_validate for colN<lengh
> +CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=10,col1=11);
> +ERROR:  col0 should not be grater than length
> diff --git a/contrib/bloom/sql/bloom.sql b/contrib/bloom/sql/bloom.sql
> index 4733e1e..0bfc767 100644
> --- a/contrib/bloom/sql/bloom.sql
> +++ b/contrib/bloom/sql/bloom.sql
> @@ -93,3 +93,6 @@ SELECT reloptions FROM pg_class WHERE oid = 'bloomidx'::regclass;
>  \set VERBOSITY terse
>  CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=0);
>  CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (col1=0);
> +
> +-- check post_validate for colN<lengh
> +CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=10,col1=11);
> diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
> index 3a0beaa..a15a10b 100644
> --- a/contrib/dblink/dblink.c
> +++ b/contrib/dblink/dblink.c
> @@ -2005,7 +2005,7 @@ PG_FUNCTION_INFO_V1(dblink_fdw_validator);
>  Datum
>  dblink_fdw_validator(PG_FUNCTION_ARGS)
>  {
> -    List       *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
> +    List       *options_list = optionsTextArrayToDefList(PG_GETARG_DATUM(0));
>      Oid            context = PG_GETARG_OID(1);
>      ListCell   *cell;
>  
> diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
> index 2c2f149..1194747 100644
> --- a/contrib/file_fdw/file_fdw.c
> +++ b/contrib/file_fdw/file_fdw.c
> @@ -195,7 +195,7 @@ file_fdw_handler(PG_FUNCTION_ARGS)
>  Datum
>  file_fdw_validator(PG_FUNCTION_ARGS)
>  {
> -    List       *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
> +    List       *options_list = optionsTextArrayToDefList(PG_GETARG_DATUM(0));
>      Oid            catalog = PG_GETARG_OID(1);
>      char       *filename = NULL;
>      DefElem    *force_not_null = NULL;
> diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
> index 5bb1af4..bbd4167 100644
> --- a/contrib/postgres_fdw/option.c
> +++ b/contrib/postgres_fdw/option.c
> @@ -72,7 +72,7 @@ PG_FUNCTION_INFO_V1(postgres_fdw_validator);
>  Datum
>  postgres_fdw_validator(PG_FUNCTION_ARGS)
>  {
> -    List       *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
> +    List       *options_list = optionsTextArrayToDefList(PG_GETARG_DATUM(0));
>      Oid            catalog = PG_GETARG_OID(1);
>      ListCell   *cell;
>  
> diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
> index ccc9fa0..5dd52a4 100644
> --- a/src/backend/access/brin/brin.c
> +++ b/src/backend/access/brin/brin.c
> @@ -20,7 +20,6 @@
>  #include "access/brin_pageops.h"
>  #include "access/brin_xlog.h"
>  #include "access/relation.h"
> -#include "access/reloptions.h"
>  #include "access/relscan.h"
>  #include "access/table.h"
>  #include "access/tableam.h"
> @@ -40,7 +39,6 @@
>  #include "utils/memutils.h"
>  #include "utils/rel.h"
>  
> -
>  /*
>   * We use a BrinBuildState during initial construction of a BRIN index.
>   * The running state is kept in a BrinMemTuple.
> @@ -119,7 +117,6 @@ brinhandler(PG_FUNCTION_ARGS)
>      amroutine->amvacuumcleanup = brinvacuumcleanup;
>      amroutine->amcanreturn = NULL;
>      amroutine->amcostestimate = brincostestimate;
> -    amroutine->amoptions = brinoptions;
>      amroutine->amproperty = NULL;
>      amroutine->ambuildphasename = NULL;
>      amroutine->amvalidate = brinvalidate;
> @@ -134,6 +131,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)
>  }
>  
>  /*
> - * 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.
>   */
> @@ -1765,3 +1746,32 @@ 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), 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 */
> +                             0,
> +                             offsetof(BrinOptions, pagesPerRange),
> +                             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,
> +                              0,
> +                              offsetof(BrinOptions, autosummarize),
> +                              false);
> +    return brin_relopt_specset;
> +}
> +
> diff --git a/src/backend/access/brin/brin_pageops.c b/src/backend/access/brin/brin_pageops.c
> index df9ffc2..1940b3d 100644
> --- a/src/backend/access/brin/brin_pageops.c
> +++ b/src/backend/access/brin/brin_pageops.c
> @@ -420,6 +420,9 @@ brin_doinsert(Relation idxrel, BlockNumber pagesPerRange,
>          freespace = br_page_get_freespace(page);
>  
>      ItemPointerSet(&tid, blk, off);
> +
> +//elog(WARNING, "pages_per_range = %i", pagesPerRange);
> +
>      brinSetHeapBlockItemptr(revmapbuf, pagesPerRange, heapBlk, tid);
>      MarkBufferDirty(revmapbuf);
>  
> diff --git a/src/backend/access/common/Makefile b/src/backend/access/common/Makefile
> index b9aff0c..78c9c5a 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 0000000..752cddc
> --- /dev/null
> +++ b/src/backend/access/common/options.c
> @@ -0,0 +1,1468 @@
> +/*-------------------------------------------------------------------------
> + *
> + * options.c
> + *      An unifom, context-free API for processing name=value options. Used
> + *      to process relation optons (reloptions), attribute options, opclass
> + *      options, etc.
> + *
> + * Portions Copyright (c) 1996-2021, 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. Implemenred 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 "sepcification" instead of "definition" because therm
> + * "definition" is used for objects that came from lexer. So to avoud confusion
> + * here we have Option Specifications, and all "definitions" are from lexer.
> + */
> +
> +/*
> + * OPTION VALUES REPRESENTATIONS
> + *
> + * Option values usually came from lexer in form of defList obect, 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.
> + *
> + * Values
> + *
> + * Values are an internal representation that is used while converting
> + * Values between other representation. Value is called "parsed",
> + * when Value's value is converted to a proper type and validated, or is called
> + * "unparsed", when Value's value is stored as raw string that was obtained
> + * from the source without any cheks. In convertation funcion names first case
> + * is refered as Values, second case is refered as RawValues. Values is
> + * implemented as List of option_value C-structures.
> + *
> + * defList
> + *
> + * Options in form of definition List that comes from lexer. (For reloptions it
> + * is a part of SQL query that goes after WITH, SET or RESET keywords). Can be
> + * converted to and from Values using optionsDefListToRawValues and
> + * optionsTextArrayToRawValues 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 Values 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 rpresentation
> + * can be obtained from Vales using optionsValuesToBytea function, and can't be
> + * converted back.
> + */
> +
> +static option_spec_basic *allocateOptionSpec(int type, const char *name,
> +                         const char *desc, LOCKMODE lockmode,
> +                         option_spec_flags flags, int struct_offset);
> +
> +static void parse_one_option(option_value * option, const char *text_str,
> +                 int text_len, bool validate);
> +static void *optionsAllocateBytea(options_spec_set * spec_set, List *options);
> +
> +
> +static List *
> +optionsDefListToRawValues(List *defList, options_parse_mode
> +                          parse_mode);
> +static Datum optionsValuesToTextArray(List *options_values);
> +static List *optionsMergeOptionValues(List *old_options, List *new_options);
> +static bytea *optionsValuesToBytea(List *options, options_spec_set * spec_set);
> +List *optionsTextArrayToRawValues(Datum array_datum);
> +List *optionsParseRawValues(List *raw_values, options_spec_set * spec_set,
> +                      options_parse_mode mode);
> +
> +
> +/*
> + * Options spec_set functions
> + */
> +
> +/*
> + * Options catalog describes options available for certain object. Catalog has
> + * all necessary information for parsing transforming and validating options
> + * for an object. All parsing/validation/transformation functions should not
> + * know any details of option implementation for certain object, all this
> + * information should be stored in catalog instead and interpreted by
> + * pars/valid/transf functions blindly.
> + *
> + * The heart of the option catalog is an array of option definitions.  Options
> + * definition specifies name of option, type, range of acceptable values, and
> + * default value.
> + *
> + * Options values can be one of the following types: bool, int, real, enum,
> + * string. For more info see "option_type" and "optionsCatalogAddItemYyyy"
> + * functions.
> + *
> + * Option definition flags allows to define parser behavior for special (or not
> + * so special) cases. See option_spec_flags for more info.
> + *
> + * 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.
> +*/
> +
> +/*
> + * 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 represenation
> + *
> + * 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 size_of_bytea, int num_items_expected)
> +{
> +    MemoryContext oldcxt;
> +    options_spec_set *spec_set;
> +
> +    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 = size_of_bytea;
> +    spec_set->postprocess_fun = NULL;
> +    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,
> +                         option_spec_flags flags, int struct_offset)
> +{
> +    MemoryContext oldcxt;
> +    size_t        size;
> +    option_spec_basic *newoption;
> +
> +    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->flags = flags;
> +    newoption->struct_offset = struct_offset;
> +
> +    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);
> +        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 *));
> +        }
> +        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, option_spec_flags flags,
> +                          int struct_offset, bool default_val)
> +{
> +    option_spec_bool *spec_set_item;
> +
> +    spec_set_item = (option_spec_bool *)
> +        allocateOptionSpec(OPTION_TYPE_BOOL, name, desc, lockmode,
> +                                 flags, struct_offset);
> +
> +    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, option_spec_flags flags,
> +                int struct_offset, 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,
> +                                 flags, struct_offset);
> +
> +    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, option_spec_flags flags, int struct_offset,
> +                          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,
> +                                 flags, struct_offset);
> +
> +    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, option_spec_flags flags, int struct_offset,
> +        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,
> +                                 flags, struct_offset);
> +
> +    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, option_spec_flags flags, int struct_offset,
> +                   const char *default_val, validate_string_option validator)
> +{
> +    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,
> +                                 flags, struct_offset);
> +    spec_set_item->validate_cb = validator;
> +
> +    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);
> +}
> +
> +
> +/*
> + * Options transform functions
> + */
> +
> +/* FIXME this comment should be updated
> + * Option values exists in five representations: DefList, TextArray, Values and
> + * Bytea:
> + *
> + * DefList: Is a List of DefElem structures, that comes from syntax analyzer.
> + * It can be transformed to Values representation for further parsing and
> + * validating
> + *
> + * Values: A List of option_value structures. Is divided into two subclasses:
> + * RawValues, when values are already transformed from DefList or TextArray,
> + * but not parsed yet. (In this case you should use raw_name and raw_value
> + * structure members to see option content). ParsedValues (or just simple
> + * Values) is crated after finding a definition for this option in a spec_set
> + * and after parsing of the raw value. For ParsedValues content is stored in
> + * values structure member, and name can be taken from option definition in gen
> + * structure member.  Actually Value list can have both Raw and Parsed values,
> + * as we do not validate options that came from database, and db option that
> + * does not exist in spec_set is just ignored, and kept as RawValues
> + *
> + * TextArray: The representation in which  options for existing object comes
> + * and goes from/to database; for example from pg_class.reloptions. It is a
> + * plain TEXT[] db object with name=value text inside. This representation can
> + * be transformed into Values for further processing, using options spec_set.
> + *
> + * Bytea: Is a binary representation of options. Each object that has code that
> + * uses options, should create a C-structure for this options, with varlen
> + * 4-byte header in front of the data; all items of options spec_set should have
> + * an offset of a corresponding binary data in this structure, so transform
> + * function can put this data in the correct place. One can transform options
> + * data from values representation into Bytea, using spec_set data, and then use
> + * it as a usual Datum object, when needed. This Datum should be cached
> + * somewhere (for example in rel->rd_options for relations) when object that
> + * has option is loaded from db.
> + */
> +
> +
> +/* optionsDefListToRawValues
> + *        Converts option values that came from syntax analyzer (DefList) into
> + *        Values List.
> + *
> + * No parsing is done here except for checking that RESET syntax is correct
> + * (syntax analyzer do not see difference between SET and RESET cases, we
> + * should treat it here manually
> + */
> +static List *
> +optionsDefListToRawValues(List *defList, options_parse_mode parse_mode)
> +{
> +    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 (parse_mode & OPTIONS_PARSE_MODE_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 List of option_values into TextArray
> + *
> + *    Convertation is made to put options into database (e.g. in
> + *    pg_class.reloptions for all relation options)
> + */
> +
> +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 options from TextArray format into RawValues list.
> + *
> + *    This function is used to convert options data that comes from database to
> + *    List of option_values, for further parsing, and, in the case of ALTER
> + *    command, for merging with new option values.
> + */
> +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
> + *        Merges two lists of option_values into one list
> + *
> + * This function is used to merge two Values list into one. It is used for all
> + * kinds of ALTER commands when existing options are merged|replaced with new
> + * options list. This function also process RESET variant of ALTER command. It
> + * merges two lists as usual, and then removes all items with RESET flag on.
> + *
> + * Both incoming lists will be destroyed while merging
> + */
> +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
> + *        Function checks that all options represented as DefList has no
> + *        namespaces or have namespaces only from allowed list
> + *
> + * Function accept options as DefList and NULL terminated list of allowed
> + * namespaces. It throws an error if not proper namespace was found.
> + *
> + * This function actually used only for tables with it's toast. namespace
> + */
> +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
> + *        Iterates over DefList, choose items with specified namespace and adds
> + *        them to a result List
> + *
> + * This function does not destroy source DefList but does not create copies
> + * of List nodes.
> + * It is actually used only for tables, in order to split toast and heap
> + * reloptions, so each one can be stored in on it's own pg_class record
> + */
> +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
> + *        Convert the text-array format of reloptions into a List of DefElem.
> + */
> +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;
> +}
> +
> +/* FIXME write comment here */
> +
> +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)
> +        {
> +            Assert(false); /* Should not get here */
> +            /* This function is used for backward compatibility in the place were namespases are not allowed */
> +            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
> + *        Parses and vlaidates (if proper flag is set) option_values. As a result
> + *        caller will get the list of parsed (or partly parsed) option_values
> + *
> + * This function is used in cases when caller gets raw values from db or
> + * syntax and want to parse them.
> + * This function uses option_spec_set to get information about how each option
> + * should be parsed.
> + * If validate mode is off, function found an option that do not have proper
> + * option_spec_set entry, this option kept unparsed (if some garbage came from
> + * the DB, we should put it back there)
> + *
> + * This function destroys incoming list.
> + */
> +List *
> +optionsParseRawValues(List *raw_values, options_spec_set * spec_set,
> +                      options_parse_mode mode)
> +{
> +    ListCell   *cell;
> +    List       *result = NIL;
> +    bool       *is_set;
> +    int            i;
> +    bool        validate = mode & OPTIONS_PARSE_MODE_VALIDATE;
> +    bool        for_alter = mode & OPTIONS_PARSE_MODE_FOR_ALTER;
> +
> +
> +    is_set = palloc0(sizeof(bool) * spec_set->num);
> +    foreach(cell, raw_values)
> +    {
> +        option_value *option = (option_value *) lfirst(cell);
> +        bool        found = false;
> +        bool        skip = false;
> +
> +
> +        if (option->status == OPTION_VALUE_STATUS_PARSED)
> +        {
> +            /*
> +             * This can happen while ALTER, when new values were already
> +             * parsed, but old values merged from DB are still raw
> +             */
> +            result = lappend(result, option);
> +            continue;
> +        }
> +        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 *definition = spec_set->definitions[i];
> +
> +            if (strcmp(option->raw_name,
> +                              definition->name) == 0)
> +            {
> +                /*
> +                 * Skip option with "ignore" flag, as it is processed
> +                 * somewhere else. (WITH OIDS special case)
> +                 */
> +                if (definition->flags & OPTION_DEFINITION_FLAG_IGNORE)
> +                {
> +                    found = true;
> +                    skip = true;
> +                    break;
> +                }
> +
> +                /*
> +                 * Reject option as if it was not in spec_set. Needed for cases
> +                 * when option should have default value, but should not be
> +                 * changed
> +                 */
> +                if (definition->flags & OPTION_DEFINITION_FLAG_REJECT)
> +                {
> +                    found = false;
> +                    break;
> +                }
> +
> +                if (validate && is_set[i])
> +                {
> +                    ereport(ERROR,
> +                            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> +                          errmsg("parameter \"%s\" specified more than once",
> +                                 option->raw_name)));
> +                }
> +                if ((for_alter) &&
> +                    (definition->flags & OPTION_DEFINITION_FLAG_FORBID_ALTER))
> +                {
> +                    ereport(ERROR,
> +                            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> +                           errmsg("changing parameter \"%s\" is not allowed",
> +                                  definition->name)));
> +                }
> +                if (option->status == OPTION_VALUE_STATUS_FOR_RESET)
> +                {
> +                    /*
> +                     * For RESET options do not need further processing so
> +                     * mark it found and stop searching
> +                     */
> +                    found = true;
> +                    break;
> +                }
> +                pfree(option->raw_name);
> +                option->raw_name = NULL;
> +                option->gen = definition;
> +                parse_one_option(option, NULL, -1, validate);
> +                is_set[i] = true;
> +                found = true;
> +                break;
> +            }
> +        }
> +        if (!found)
> +        {
> +            if (validate)
> +            {
> +                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)));
> +            } else
> +            {
> +                /* RESET is always in non-validating mode, unkown names should
> +                 * be ignored. This is traditional behaviour of postgres/
> +                 * FIXME may be it should be changed someday
> +                 */
> +                if (option->status == OPTION_VALUE_STATUS_FOR_RESET)
> +                {
> +                    skip = true;
> +                }
> +            }
> +            /*
> +             * In other cases, if we are parsing not in validate mode, then
> +             * we should keep unknown node, because non-validate mode is for
> +             * data that is already in the DB and should not be changed after
> +             * altering another entries
> +             */
> +        }
> +        if (!skip)
> +            result = lappend(result, option);
> +    }
> +    return result;
> +}
> +
> +/*
> + * parse_one_option
> + *
> + *        Subroutine for optionsParseRawValues, to parse and validate a
> + *        single option's value
> + */
> +static void
> +parse_one_option(option_value * option, const char *text_str, int text_len,
> +                 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 (parsed)
> +        option->status = OPTION_VALUE_STATUS_PARSED;
> +
> +}
> +
> +/*
> + * optionsAllocateBytea
> + *        Allocates memory for bytea options representation
> + *
> + * Function allocates memory for byrea structure of an option, plus adds space
> + * for values of string options. We should keep all data including string
> + * values in the same memory chunk, because Cache code copies bytea option
> + * data from one MemoryConext to another without knowing about it's internal
> + * structure, so it would not be able to copy string values if they are outside
> + * of bytea memory chunk.
> + */
> +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 *definition = spec_set->definitions[i];
> +        bool        found = false;
> +        option_value *option;
> +
> +        /* Not interested in non-string options, skipping */
> +        if (definition->type != OPTION_TYPE_STRING)
> +            continue;
> +
> +        /*
> +         * Trying to find option_value that references definition spec_set
> +         * entry
> +         */
> +        foreach(cell, options)
> +        {
> +            option = (option_value *) lfirst(cell);
> +            if (option->status == OPTION_VALUE_STATUS_PARSED &&
> +                strcmp(option->gen->name, definition->name) == 0)
> +            {
> +                found = true;
> +                break;
> +            }
> +        }
> +        if (found)
> +            /* If found, it'value will be stored */
> +            length = strlen(option->values.string_val) + 1;
> +        else
> +            /* If not found, then there would be default value there */
> +        if (((option_spec_string *) definition)->default_val)
> +            length = strlen(
> +                 ((option_spec_string *) definition)->default_val) + 1;
> +        else
> +            length = 0;
> +        /* Add total length of all string values to basic size */
> +        size += length;
> +    }
> +
> +    res = palloc0(size);
> +    SET_VARSIZE(res, size);
> +    return res;
> +}
> +
> +/*
> + * optionsValuesToBytea
> + *        Converts options from List of option_values to binary bytea structure
> + *
> + * Convertation goes according to options_spec_set: each spec_set item
> + * has offset value, and option value in binary mode is written to the
> + * structure with that offset.
> + *
> + * More special case is string values. Memory for bytea structure is allocated
> + * by optionsAllocateBytea which adds some more space for string values to
> + * the size of original structure. All string values are copied there and
> + * inside the bytea structure an offset to that value is kept.
> + *
> + */
> +static 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 *definition = spec_set->definitions[i];
> +
> +        if (definition->flags & OPTION_DEFINITION_FLAG_IGNORE)
> +            continue;
> +
> +        /* Calculate the position of the item inside the structure */
> +        item_pos = data + definition->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(definition->name, option->gen->name) == 0)
> +            {
> +                found = option;
> +                break;
> +            }
> +        }
> +        /* writing to the proper position either option value or default val */
> +        switch (definition->type)
> +        {
> +            case OPTION_TYPE_BOOL:
> +                *(bool *) item_pos = found ?
> +                    found->values.bool_val :
> +                    ((option_spec_bool *) definition)->default_val;
> +                break;
> +            case OPTION_TYPE_INT:
> +                *(int *) item_pos = found ?
> +                    found->values.int_val :
> +                    ((option_spec_int *) definition)->default_val;
> +                break;
> +            case OPTION_TYPE_REAL:
> +                *(double *) item_pos = found ?
> +                    found->values.real_val :
> +                    ((option_spec_real *) definition)->default_val;
> +                break;
> +            case OPTION_TYPE_ENUM:
> +                *(int *) item_pos = found ?
> +                    found->values.enum_val :
> +                    ((option_spec_enum *) definition)->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;
> +
> +                    if (found)
> +                        value = found->values.string_val;
> +                    else
> +                        value = ((option_spec_string *) definition)
> +                            ->default_val;
> +                    *(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",
> +                     definition->type);
> +                break;
> +        }
> +    }
> +    return (void *) data;
> +}
> +
> +
> +/*
> + * transformOptions
> + *        This function is used by src/backend/commands/Xxxx in order to process
> + *        new option values, merge them with existing values (in the case of
> + *        ALTER command) and prepare to put them [back] into DB
> + */
> +
> +Datum
> +transformOptions(options_spec_set * spec_set, Datum oldOptions,
> +                 List *defList, options_parse_mode parse_mode)
> +{
> +    Datum        result;
> +    List       *new_values;
> +    List       *old_values;
> +    List       *merged_values;
> +
> +    /*
> +     * Parse and validate New values
> +     */
> +    new_values = optionsDefListToRawValues(defList, parse_mode);
> +    if (! (parse_mode & OPTIONS_PARSE_MODE_FOR_RESET))
> +    {
> +        /* FIXME: postgres usual behaviour vas not to vaidate names that
> +         * came from RESET command. Once this behavious should be changed,
> +         * I guess. But for now we keep it as it was.
> +         */
> +        parse_mode|= OPTIONS_PARSE_MODE_VALIDATE;
> +    }
> +    new_values = optionsParseRawValues(new_values, spec_set, parse_mode);
> +
> +    /*
> +     * Old values exists in case of ALTER commands. Transform them to raw
> +     * values and merge them with new_values, and parse it.
> +     */
> +    if (PointerIsValid(DatumGetPointer(oldOptions)))
> +    {
> +        old_values = optionsTextArrayToRawValues(oldOptions);
> +        merged_values = optionsMergeOptionValues(old_values, new_values);
> +
> +        /*
> +         * Parse options only after merging in order not to parse options that
> +         * would be removed by merging later
> +         */
> +        merged_values = optionsParseRawValues(merged_values, spec_set, 0);
> +    }
> +    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
> + *        A meta-function that transforms options stored as TextArray into binary
> + *        (bytea) representation.
> + *
> + *    This function runs other transform functions that leads to the desired
> + *    result in no-validation mode. This function is used by cache mechanism,
> + *    in order to load and cache options when object itself is loaded and cached
> + */
> +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_PARSE_MODE_VALIDATE : 0);
> +    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 632d13c..49ad197 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 b5602f5..29ab98a 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-2021, PostgreSQL Global Development Group
>   * Portions Copyright (c) 1994, Regents of the University of California
> @@ -17,13 +17,10 @@
>  
>  #include <float.h>
>  
> -#include "access/gist_private.h"
> -#include "access/hash.h"
>  #include "access/heaptoast.h"
>  #include "access/htup_details.h"
> -#include "access/nbtree.h"
>  #include "access/reloptions.h"
> -#include "access/spgist_private.h"
> +#include "access/options.h"
>  #include "catalog/pg_type.h"
>  #include "commands/defrem.h"
>  #include "commands/tablespace.h"
> @@ -36,6 +33,7 @@
>  #include "utils/guc.h"
>  #include "utils/memutils.h"
>  #include "utils/rel.h"
> +#include "storage/bufmgr.h"
>  
>  /*
>   * Contents of pg_class.reloptions
> @@ -93,380 +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
> -    },
> -    {
> -        {
> -            "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},
> @@ -480,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},
> @@ -498,61 +115,8 @@ 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);
> +options_spec_set *get_stdrd_relopt_spec_set(relopt_kind kind);
>  
>  /*
>   * Get the length of a string reloption (either default or the user-defined
> @@ -563,160 +127,6 @@ static void parse_one_reloption(relopt_value *option, char *text_str,
>      ((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;
> -}
>  
>  /*
>   * init_local_reloptions
> @@ -729,6 +139,7 @@ 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, 0);
>  }
>  
>  /*
> @@ -743,112 +154,6 @@ register_reloptions_validator(local_relopts *opts, relopts_validator 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
>   *
> @@ -858,47 +163,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, 0, offset,
> +                                                                default_val);
>  }
>  
>  /*
> @@ -912,47 +178,8 @@ add_local_int_reloption(local_relopts *relopts, const char *name,
>                          const char *desc, int default_val, int min_val,
>                          int max_val, int offset)
>  {
> -    relopt_int *newoption = init_int_reloption(RELOPT_KIND_LOCAL,
> -                                               name, desc, default_val,
> -                                               min_val, max_val, 0);
> -
> -    add_local_reloption(relopts, (relopt_gen *) newoption, offset);
> -}
> -
> -/*
> - * init_real_reloption
> - *        Allocate and initialize a new real reloption
> - */
> -static relopt_real *
> -init_real_reloption(bits32 kinds, const char *name, const char *desc,
> -                    double default_val, double min_val, double max_val,
> -                    LOCKMODE lockmode)
> -{
> -    relopt_real *newoption;
> -
> -    newoption = (relopt_real *) allocate_reloption(kinds, RELOPT_TYPE_REAL,
> -                                                   name, desc, lockmode);
> -    newoption->default_val = default_val;
> -    newoption->min = min_val;
> -    newoption->max = max_val;
> -
> -    return newoption;
> -}
> -
> -/*
> - * add_real_reloption
> - *        Add a new float reloption
> - */
> -void
> -add_real_reloption(bits32 kinds, const char *name, const char *desc,
> -                   double default_val, double min_val, double max_val,
> -                   LOCKMODE lockmode)
> -{
> -    relopt_real *newoption = init_real_reloption(kinds, name, desc,
> -                                                 default_val, min_val,
> -                                                 max_val, lockmode);
> -
> -    add_reloption((relopt_gen *) newoption);
> +    optionsSpecSetAddInt(relopts->spec_set, name, desc, NoLock, 0, offset,
> +                                                default_val, min_val, max_val);
>  }
>  
>  /*
> @@ -966,57 +193,9 @@ add_local_real_reloption(local_relopts *relopts, const char *name,
>                           const char *desc, double default_val,
>                           double min_val, double max_val, int offset)
>  {
> -    relopt_real *newoption = init_real_reloption(RELOPT_KIND_LOCAL,
> -                                                 name, desc,
> -                                                 default_val, min_val,
> -                                                 max_val, 0);
> -
> -    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);
> +    optionsSpecSetAddReal(relopts->spec_set, name, desc, NoLock, 0, offset,
> +                                                default_val, min_val, max_val);
>  
> -    add_reloption((relopt_gen *) newoption);
>  }
>  
>  /*
> @@ -1027,77 +206,11 @@ add_enum_reloption(bits32 kinds, const char *name, const char *desc,
>   */
>  void
>  add_local_enum_reloption(local_relopts *relopts, const char *name,
> -                         const char *desc, relopt_enum_elt_def *members,
> +                         const char *desc, opt_enum_elt_def *members,
>                           int default_val, const char *detailmsg, int offset)
>  {
> -    relopt_enum *newoption = init_enum_reloption(RELOPT_KIND_LOCAL,
> -                                                 name, desc,
> -                                                 members, default_val,
> -                                                 detailmsg, 0);
> -
> -    add_local_reloption(relopts, (relopt_gen *) newoption, offset);
> -}
> -
> -/*
> - * init_string_reloption
> - *        Allocate and initialize a new string reloption
> - */
> -static relopt_string *
> -init_string_reloption(bits32 kinds, const char *name, const char *desc,
> -                      const char *default_val,
> -                      validate_string_relopt validator,
> -                      fill_string_relopt filler,
> -                      LOCKMODE lockmode)
> -{
> -    relopt_string *newoption;
> -
> -    /* make sure the validator/default combination is sane */
> -    if (validator)
> -        (validator) (default_val);
> -
> -    newoption = (relopt_string *) allocate_reloption(kinds, RELOPT_TYPE_STRING,
> -                                                     name, desc, lockmode);
> -    newoption->validate_cb = validator;
> -    newoption->fill_cb = filler;
> -    if (default_val)
> -    {
> -        if (kinds == RELOPT_KIND_LOCAL)
> -            newoption->default_val = strdup(default_val);
> -        else
> -            newoption->default_val = MemoryContextStrdup(TopMemoryContext, default_val);
> -        newoption->default_len = strlen(default_val);
> -        newoption->default_isnull = false;
> -    }
> -    else
> -    {
> -        newoption->default_val = "";
> -        newoption->default_len = 0;
> -        newoption->default_isnull = true;
> -    }
> -
> -    return newoption;
> -}
> -
> -/*
> - * add_string_reloption
> - *        Add a new string reloption
> - *
> - * "validator" is an optional function pointer that can be used to test the
> - * validity of the values.  It must elog(ERROR) when the argument string is
> - * not acceptable for the variable.  Note that the default value must pass
> - * the validation.
> - */
> -void
> -add_string_reloption(bits32 kinds, const char *name, const char *desc,
> -                     const char *default_val, validate_string_relopt validator,
> -                     LOCKMODE lockmode)
> -{
> -    relopt_string *newoption = init_string_reloption(kinds, name, desc,
> -                                                     default_val,
> -                                                     validator, NULL,
> -                                                     lockmode);
> -
> -    add_reloption((relopt_gen *) newoption);
> +    optionsSpecSetAddEnum(relopts->spec_set, name, desc, NoLock, 0, offset,
> +                                            members, default_val, detailmsg);
>  }
>  
>  /*
> @@ -1113,249 +226,9 @@ add_local_string_reloption(local_relopts *relopts, const char *name,
>                             validate_string_relopt validator,
>                             fill_string_relopt filler, int offset)
>  {
> -    relopt_string *newoption = init_string_reloption(RELOPT_KIND_LOCAL,
> -                                                     name, desc,
> -                                                     default_val,
> -                                                     validator, filler,
> -                                                     0);
> -
> -    add_local_reloption(relopts, (relopt_gen *) newoption, offset);
> -}
> -
> -/*
> - * Transform a relation options list (list of DefElem) into the text array
> - * format that is kept in pg_class.reloptions, including only those options
> - * that are in the passed namespace.  The output values do not include the
> - * namespace.
> - *
> - * This is used for three cases: CREATE TABLE/INDEX, ALTER TABLE SET, and
> - * ALTER TABLE RESET.  In the ALTER cases, oldOptions is the existing
> - * reloptions value (possibly NULL), and we replace or remove entries
> - * as needed.
> - *
> - * If acceptOidsOff is true, then we allow oids = false, but throw error when
> - * on. This is solely needed for backwards compatibility.
> - *
> - * Note that this is not responsible for determining whether the options
> - * are valid, but it does check that namespaces for all the options given are
> - * listed in validnsps.  The NULL namespace is always valid and need not be
> - * explicitly listed.  Passing a NULL pointer means that only the NULL
> - * namespace is valid.
> - *
> - * Both oldOptions and the result are text arrays (or NULL for "default"),
> - * but we declare them as Datums to avoid including array.h in reloptions.h.
> - */
> -Datum
> -transformRelOptions(Datum oldOptions, List *defList, const char *namspace,
> -                    char *validnsps[], bool acceptOidsOff, bool isReset)
> -{
> -    Datum        result;
> -    ArrayBuildState *astate;
> -    ListCell   *cell;
> -
> -    /* no change if empty list */
> -    if (defList == NIL)
> -        return oldOptions;
> -
> -    /* We build new array using accumArrayResult */
> -    astate = NULL;
> -
> -    /* Copy any oldOptions that aren't to be replaced */
> -    if (PointerIsValid(DatumGetPointer(oldOptions)))
> -    {
> -        ArrayType  *array = DatumGetArrayTypeP(oldOptions);
> -        Datum       *oldoptions;
> -        int            noldoptions;
> -        int            i;
> -
> -        deconstruct_array(array, TEXTOID, -1, false, TYPALIGN_INT,
> -                          &oldoptions, NULL, &noldoptions);
> -
> -        for (i = 0; i < noldoptions; i++)
> -        {
> -            char       *text_str = VARDATA(oldoptions[i]);
> -            int            text_len = VARSIZE(oldoptions[i]) - VARHDRSZ;
> -
> -            /* Search for a match in defList */
> -            foreach(cell, defList)
> -            {
> -                DefElem    *def = (DefElem *) lfirst(cell);
> -                int            kw_len;
> -
> -                /* ignore if not in the same namespace */
> -                if (namspace == NULL)
> -                {
> -                    if (def->defnamespace != NULL)
> -                        continue;
> -                }
> -                else if (def->defnamespace == NULL)
> -                    continue;
> -                else if (strcmp(def->defnamespace, namspace) != 0)
> -                    continue;
> -
> -                kw_len = strlen(def->defname);
> -                if (text_len > kw_len && text_str[kw_len] == '=' &&
> -                    strncmp(text_str, def->defname, kw_len) == 0)
> -                    break;
> -            }
> -            if (!cell)
> -            {
> -                /* No match, so keep old option */
> -                astate = accumArrayResult(astate, oldoptions[i],
> -                                          false, TEXTOID,
> -                                          CurrentMemoryContext);
> -            }
> -        }
> -    }
> -
> -    /*
> -     * If CREATE/SET, add new options to array; if RESET, just check that the
> -     * user didn't say RESET (option=val).  (Must do this because the grammar
> -     * doesn't enforce it.)
> -     */
> -    foreach(cell, defList)
> -    {
> -        DefElem    *def = (DefElem *) lfirst(cell);
> -
> -        if (isReset)
> -        {
> -            if (def->arg != NULL)
> -                ereport(ERROR,
> -                        (errcode(ERRCODE_SYNTAX_ERROR),
> -                         errmsg("RESET must not include values for parameters")));
> -        }
> -        else
> -        {
> -            text       *t;
> -            const char *value;
> -            Size        len;
> -
> -            /*
> -             * Error out if the namespace is not valid.  A NULL namespace is
> -             * always valid.
> -             */
> -            if (def->defnamespace != NULL)
> -            {
> -                bool        valid = false;
> -                int            i;
> -
> -                if (validnsps)
> -                {
> -                    for (i = 0; validnsps[i]; i++)
> -                    {
> -                        if (strcmp(def->defnamespace, validnsps[i]) == 0)
> -                        {
> -                            valid = true;
> -                            break;
> -                        }
> -                    }
> -                }
> -
> -                if (!valid)
> -                    ereport(ERROR,
> -                            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> -                             errmsg("unrecognized parameter namespace \"%s\"",
> -                                    def->defnamespace)));
> -            }
> -
> -            /* ignore if not in the same namespace */
> -            if (namspace == NULL)
> -            {
> -                if (def->defnamespace != NULL)
> -                    continue;
> -            }
> -            else if (def->defnamespace == NULL)
> -                continue;
> -            else if (strcmp(def->defnamespace, namspace) != 0)
> -                continue;
> -
> -            /*
> -             * Flatten the DefElem into a text string like "name=arg". If we
> -             * have just "name", assume "name=true" is meant.  Note: the
> -             * namespace is not output.
> -             */
> -            if (def->arg != NULL)
> -                value = defGetString(def);
> -            else
> -                value = "true";
> -
> -            /*
> -             * This is not a great place for this test, but there's no other
> -             * convenient place to filter the option out. As WITH (oids =
> -             * false) will be removed someday, this seems like an acceptable
> -             * amount of ugly.
> -             */
> -            if (acceptOidsOff && def->defnamespace == NULL &&
> -                strcmp(def->defname, "oids") == 0)
> -            {
> -                if (defGetBoolean(def))
> -                    ereport(ERROR,
> -                            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
> -                             errmsg("tables declared WITH OIDS are not supported")));
> -                /* skip over option, reloptions machinery doesn't know it */
> -                continue;
> -            }
> -
> -            len = VARHDRSZ + strlen(def->defname) + 1 + strlen(value);
> -            /* +1 leaves room for sprintf's trailing null */
> -            t = (text *) palloc(len + 1);
> -            SET_VARSIZE(t, len);
> -            sprintf(VARDATA(t), "%s=%s", def->defname, value);
> -
> -            astate = accumArrayResult(astate, PointerGetDatum(t),
> -                                      false, TEXTOID,
> -                                      CurrentMemoryContext);
> -        }
> -    }
> -
> -    if (astate)
> -        result = makeArrayResult(astate, CurrentMemoryContext);
> -    else
> -        result = (Datum) 0;
> -
> -    return result;
> -}
> -
> -
> -/*
> - * Convert the text-array format of reloptions into a List of DefElem.
> - * This is the inverse of transformRelOptions().
> - */
> -List *
> -untransformRelOptions(Datum options)
> -{
> -    List       *result = NIL;
> -    ArrayType  *array;
> -    Datum       *optiondatums;
> -    int            noptions;
> -    int            i;
> -
> -    /* Nothing to do if no options */
> -    if (!PointerIsValid(DatumGetPointer(options)))
> -        return result;
> -
> -    array = DatumGetArrayTypeP(options);
> -
> -    deconstruct_array(array, TEXTOID, -1, false, TYPALIGN_INT,
> -                      &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;
> +    optionsSpecSetAddString(relopts->spec_set, name, desc, NoLock, 0, offset,
> +                                            default_val, validator);
> +/* FIXME solve mistery with filler option! */
>  }
>  
>  /*
> @@ -1372,12 +245,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,
> @@ -1394,702 +268,341 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
>          case RELKIND_RELATION:
>          case RELKIND_TOASTVALUE:
>          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)
> -{
> -    ArrayType  *array = DatumGetArrayTypeP(options);
> -    Datum       *optiondatums;
> -    int            noptions;
> -    int            i;
> -
> -    deconstruct_array(array, TEXTOID, -1, false, TYPALIGN_INT,
> -                      &optiondatums, NULL, &noptions);
> +options_spec_set *
> +get_stdrd_relopt_spec_set(relopt_kind kind)
> +{
> +    bool is_for_toast = (kind == RELOPT_KIND_TOAST);
> +
> +    options_spec_set * stdrd_relopt_spec_set = allocateOptionsSpecSet(
> +                    is_for_toast ? "toast" : NULL,  sizeof(StdRdOptions), 0); //FIXME change 0 to actual value (may
be)
> +    optionsSpecSetAddInt(stdrd_relopt_spec_set, "fillfactor",
> +                                 "Packs table pages only to this percentag",
> +                                 ShareUpdateExclusiveLock,        /* since it applies only
> +                                                                 * to later inserts */
> +                                is_for_toast ? OPTION_DEFINITION_FLAG_REJECT : 0,
> +                                offsetof(StdRdOptions, fillfactor),
> +                          HEAP_DEFAULT_FILLFACTOR, HEAP_MIN_FILLFACTOR, 100);
> +    optionsSpecSetAddBool(stdrd_relopt_spec_set, "autovacuum_enabled",
> +                              "Enables autovacuum in this relation",
> +                              ShareUpdateExclusiveLock, 0,
> +            offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled),
> +                              true);
> +    optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_vacuum_threshold",
> +                "Minimum number of tuple updates or deletes prior to vacuum",
> +                             ShareUpdateExclusiveLock,
> +                    0, offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_threshold),
> +                             -1, 0, INT_MAX);
> +    optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_analyze_threshold",
> +                "Minimum number of tuple updates or deletes prior to vacuum",
> +                             ShareUpdateExclusiveLock,
> +                             is_for_toast ? OPTION_DEFINITION_FLAG_REJECT : 0,
> +                      offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_threshold),
> +                             -1, 0, INT_MAX);
> +    optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_vacuum_cost_limit",
> +               "Vacuum cost amount available before napping, for autovacuum",
> +                             ShareUpdateExclusiveLock,
> +                   0, offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_limit),
> +                             -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,
> +                      0, offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_min_age),
> +                             -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,
> +                      0, offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_max_age),
> +                             -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,
> +                    0, offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_table_age),
> +                             -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,
> +            0, offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_min_age),
> +                             -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,
> +            0, offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_max_age),
> +                             -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,
> +          0, offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_table_age),
> +                             -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,
> +                    0, offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, log_min_duration),
> +                             -1, -1, INT_MAX);
> +    optionsSpecSetAddReal(stdrd_relopt_spec_set, "autovacuum_vacuum_cost_delay",
> +                         "Vacuum cost delay in milliseconds, for autovacuum",
> +                             ShareUpdateExclusiveLock,
> +                   0, offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_delay),
> +                             -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,
> +                 0, offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_scale_factor),
> +                              -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,
> +                 0, offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_ins_scale_factor),
> +                              -1, 0.0, 100.0);
> +
> +    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,
> +                              is_for_toast ? OPTION_DEFINITION_FLAG_REJECT : 0,
> +                   offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_scale_factor),
> +                              -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,
> +                                is_for_toast ? OPTION_DEFINITION_FLAG_REJECT : 0,
> +                                offsetof(StdRdOptions, toast_tuple_target),
> +                          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,
> +                                is_for_toast ? OPTION_DEFINITION_FLAG_REJECT : 0,
> +                                 offsetof(StdRdOptions, user_catalog_table),
> +                                  false);
> +
> +    optionsSpecSetAddInt(stdrd_relopt_spec_set, "parallel_workers",
> +                                "Number of parallel processes that can be used per executor node for this
relation.",
> +                                ShareUpdateExclusiveLock,
> +                                is_for_toast ? OPTION_DEFINITION_FLAG_REJECT : 0,
> +                                offsetof(StdRdOptions, parallel_workers),
> +                                -1, 0, 1024);
> +
> +    optionsSpecSetAddEnum(stdrd_relopt_spec_set, "vacuum_index_cleanup",
> +                                "Controls index vacuuming and index cleanup",
> +                                ShareUpdateExclusiveLock, 0,
> +                                offsetof(StdRdOptions, vacuum_index_cleanup),
> +                                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, 0,
> +                                offsetof(StdRdOptions, vacuum_truncate),
> +                                true);
> +
> +// FIXME Do something with OIDS
> +
> +    return stdrd_relopt_spec_set;
> +}
> +
> +
> +static options_spec_set *heap_relopt_spec_set = NULL;
> +
> +options_spec_set *
> +get_heap_relopt_spec_set(void)
> +{
> +    if (heap_relopt_spec_set)
> +        return heap_relopt_spec_set;
> +    heap_relopt_spec_set = get_stdrd_relopt_spec_set(RELOPT_KIND_HEAP);
> +    return heap_relopt_spec_set;
> +}
> +
> +static options_spec_set *toast_relopt_spec_set = NULL;
> +
> +options_spec_set *
> +get_toast_relopt_spec_set(void)
> +{
> +    if (toast_relopt_spec_set)
> +        return toast_relopt_spec_set;
> +    toast_relopt_spec_set = get_stdrd_relopt_spec_set(RELOPT_KIND_TOAST);
> +    return toast_relopt_spec_set;
> +}
> +
> +static options_spec_set *partitioned_relopt_spec_set = NULL;
>  
> -    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);
> +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), 0);
> +    /* No options for now, so spec set is empty */
>  
> -    if (((void *) array) != DatumGetPointer(options))
> -        pfree(array);
> +    return partitioned_relopt_spec_set;
>  }
>  
>  /*
> - * 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.
> + * 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 relopt_value *
> -parseRelOptions(Datum options, bool validate, relopt_kind kind,
> -                int *numrelopts)
> -{
> -    relopt_value *reloptions = NULL;
> -    int            numoptions = 0;
> -    int            i;
> -    int            j;
> -
> -    if (need_initialization)
> -        initialize_reloptions();
> -
> -    /* Build a list of expected options, based on kind */
> -
> -    for (i = 0; relOpts[i]; i++)
> -        if (relOpts[i]->kinds & kind)
> -            numoptions++;
> -
> -    if (numoptions > 0)
> -    {
> -        reloptions = palloc(numoptions * sizeof(relopt_value));
> -
> -        for (i = 0, j = 0; relOpts[i]; i++)
> -        {
> -            if (relOpts[i]->kinds & kind)
> -            {
> -                reloptions[j].gen = relOpts[i];
> -                reloptions[j].isset = false;
> -                j++;
> -            }
> -        }
> -    }
> -
> -    /* Done if no options */
> -    if (PointerIsValid(DatumGetPointer(options)))
> -        parseRelOptionsInternal(options, validate, reloptions, numoptions);
> -
> -    *numrelopts = numoptions;
> -    return reloptions;
> -}
> -
> -/* Parse local unregistered options. */
> -static relopt_value *
> -parseLocalRelOptions(local_relopts *relopts, Datum options, bool validate)
> +void *
> +build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
>  {
> -    int            nopts = list_length(relopts->options);
> -    relopt_value *values = palloc(sizeof(*values) * nopts);
> +    void       *opts;
>      ListCell   *lc;
> -    int            i = 0;
> -
> -    foreach(lc, relopts->options)
> -    {
> -        local_relopt *opt = lfirst(lc);
> -
> -        values[i].gen = opt->option;
> -        values[i].isset = false;
> -
> -        i++;
> -    }
> -
> -    if (options != (Datum) 0)
> -        parseRelOptionsInternal(options, validate, values, nopts);
> +    opts = (void *) optionsTextArrayToBytea(relopts->spec_set, options, validate);
>  
> -    return values;
> -}
> -
> -/*
> - * Subroutine for parseRelOptions, to parse and validate a single option's
> - * value
> - */
> -static void
> -parse_one_reloption(relopt_value *option, char *text_str, int text_len,
> -                    bool validate)
> -{
> -    char       *value;
> -    int            value_len;
> -    bool        parsed;
> -    bool        nofree = false;
> -
> -    if (option->isset && validate)
> -        ereport(ERROR,
> -                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> -                 errmsg("parameter \"%s\" specified more than once",
> -                        option->gen->name)));
> -
> -    value_len = text_len - option->gen->namelen - 1;
> -    value = (char *) palloc(value_len + 1);
> -    memcpy(value, text_str + option->gen->namelen + 1, value_len);
> -    value[value_len] = '\0';
> -
> -    switch (option->gen->type)
> -    {
> -        case RELOPT_TYPE_BOOL:
> -            {
> -                parsed = parse_bool(value, &option->values.bool_val);
> -                if (validate && !parsed)
> -                    ereport(ERROR,
> -                            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> -                             errmsg("invalid value for boolean option \"%s\": %s",
> -                                    option->gen->name, value)));
> -            }
> -            break;
> -        case RELOPT_TYPE_INT:
> -            {
> -                relopt_int *optint = (relopt_int *) option->gen;
> -
> -                parsed = parse_int(value, &option->values.int_val, 0, NULL);
> -                if (validate && !parsed)
> -                    ereport(ERROR,
> -                            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> -                             errmsg("invalid value for integer option \"%s\": %s",
> -                                    option->gen->name, value)));
> -                if (validate && (option->values.int_val < optint->min ||
> -                                 option->values.int_val > optint->max))
> -                    ereport(ERROR,
> -                            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> -                             errmsg("value %s out of bounds for option \"%s\"",
> -                                    value, option->gen->name),
> -                             errdetail("Valid values are between \"%d\" and \"%d\".",
> -                                       optint->min, optint->max)));
> -            }
> -            break;
> -        case RELOPT_TYPE_REAL:
> -            {
> -                relopt_real *optreal = (relopt_real *) option->gen;
> -
> -                parsed = parse_real(value, &option->values.real_val, 0, NULL);
> -                if (validate && !parsed)
> -                    ereport(ERROR,
> -                            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> -                             errmsg("invalid value for floating point option \"%s\": %s",
> -                                    option->gen->name, value)));
> -                if (validate && (option->values.real_val < optreal->min ||
> -                                 option->values.real_val > optreal->max))
> -                    ereport(ERROR,
> -                            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> -                             errmsg("value %s out of bounds for option \"%s\"",
> -                                    value, option->gen->name),
> -                             errdetail("Valid values are between \"%f\" and \"%f\".",
> -                                       optreal->min, optreal->max)));
> -            }
> -            break;
> -        case RELOPT_TYPE_ENUM:
> -            {
> -                relopt_enum *optenum = (relopt_enum *) option->gen;
> -                relopt_enum_elt_def *elt;
> -
> -                parsed = false;
> -                for (elt = optenum->members; elt->string_val; elt++)
> -                {
> -                    if (pg_strcasecmp(value, elt->string_val) == 0)
> -                    {
> -                        option->values.enum_val = elt->symbol_val;
> -                        parsed = true;
> -                        break;
> -                    }
> -                }
> -                if (validate && !parsed)
> -                    ereport(ERROR,
> -                            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> -                             errmsg("invalid value for enum option \"%s\": %s",
> -                                    option->gen->name, value),
> -                             optenum->detailmsg ?
> -                             errdetail_internal("%s", _(optenum->detailmsg)) : 0));
> -
> -                /*
> -                 * If value is not among the allowed string values, but we are
> -                 * not asked to validate, just use the default numeric value.
> -                 */
> -                if (!parsed)
> -                    option->values.enum_val = optenum->default_val;
> -            }
> -            break;
> -        case RELOPT_TYPE_STRING:
> -            {
> -                relopt_string *optstring = (relopt_string *) option->gen;
> -
> -                option->values.string_val = value;
> -                nofree = true;
> -                if (validate && optstring->validate_cb)
> -                    (optstring->validate_cb) (value);
> -                parsed = true;
> -            }
> -            break;
> -        default:
> -            elog(ERROR, "unsupported reloption type %d", option->gen->type);
> -            parsed = true;        /* quiet compiler */
> -            break;
> -    }
> +    foreach(lc, relopts->validators)
> +        ((relopts_validator) lfirst(lc)) (opts, NULL, 0);
> +//        ((relopts_validator) lfirst(lc)) (opts, vals, noptions);
> +// FIXME solve problem with validation of separate option values;
> +    return opts;
>  
> -    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).
> + * get_view_relopt_spec_set
> + *        Returns an options catalog for view relation.
>   */
> -static void *
> -allocateReloptStruct(Size base, relopt_value *options, int numoptions)
> -{
> -    Size        size = base;
> -    int            i;
> -
> -    for (i = 0; i < numoptions; i++)
> -    {
> -        relopt_value *optval = &options[i];
> -
> -        if (optval->gen->type == RELOPT_TYPE_STRING)
> -        {
> -            relopt_string *optstr = (relopt_string *) optval->gen;
> -
> -            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;
> -        }
> -    }
> -
> -    return palloc0(size);
> -}
> +static options_spec_set *view_relopt_spec_set = NULL;
>  
> -/*
> - * 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)
> +options_spec_set *
> +get_view_relopt_spec_set(void)
>  {
> -    int            i;
> -    int            offset = basesize;
> +    if (view_relopt_spec_set)
> +        return view_relopt_spec_set;
>  
> -    for (i = 0; i < numoptions; i++)
> -    {
> -        int            j;
> -        bool        found = false;
> +    view_relopt_spec_set = allocateOptionsSpecSet(NULL,
> +                                                 sizeof(ViewOptions), 2);
>  
> -        for (j = 0; j < numelems; j++)
> -        {
> -            if (strcmp(options[i].gen->name, elems[j].optname) == 0)
> -            {
> -                relopt_string *optstring;
> -                char       *itempos = ((char *) rdopts) + elems[j].offset;
> -                char       *string_val;
> -
> -                switch (options[i].gen->type)
> -                {
> -                    case RELOPT_TYPE_BOOL:
> -                        *(bool *) itempos = options[i].isset ?
> -                            options[i].values.bool_val :
> -                            ((relopt_bool *) options[i].gen)->default_val;
> -                        break;
> -                    case RELOPT_TYPE_INT:
> -                        *(int *) itempos = options[i].isset ?
> -                            options[i].values.int_val :
> -                            ((relopt_int *) options[i].gen)->default_val;
> -                        break;
> -                    case RELOPT_TYPE_REAL:
> -                        *(double *) itempos = options[i].isset ?
> -                            options[i].values.real_val :
> -                            ((relopt_real *) options[i].gen)->default_val;
> -                        break;
> -                    case RELOPT_TYPE_ENUM:
> -                        *(int *) itempos = options[i].isset ?
> -                            options[i].values.enum_val :
> -                            ((relopt_enum *) options[i].gen)->default_val;
> -                        break;
> -                    case RELOPT_TYPE_STRING:
> -                        optstring = (relopt_string *) options[i].gen;
> -                        if (options[i].isset)
> -                            string_val = options[i].values.string_val;
> -                        else if (!optstring->default_isnull)
> -                            string_val = optstring->default_val;
> -                        else
> -                            string_val = NULL;
> -
> -                        if (optstring->fill_cb)
> -                        {
> -                            Size        size =
> -                            optstring->fill_cb(string_val,
> -                                               (char *) rdopts + offset);
> -
> -                            if (size)
> -                            {
> -                                *(int *) itempos = offset;
> -                                offset += size;
> -                            }
> -                            else
> -                                *(int *) itempos = 0;
> -                        }
> -                        else if (string_val == NULL)
> -                            *(int *) itempos = 0;
> -                        else
> -                        {
> -                            strcpy((char *) rdopts + offset, string_val);
> -                            *(int *) itempos = offset;
> -                            offset += strlen(string_val) + 1;
> -                        }
> -                        break;
> -                    default:
> -                        elog(ERROR, "unsupported reloption type %d",
> -                             options[i].gen->type);
> -                        break;
> -                }
> -                found = true;
> -                break;
> -            }
> -        }
> -        if (validate && !found)
> -            elog(ERROR, "reloption \"%s\" not found in parse table",
> -                 options[i].gen->name);
> -    }
> -    SET_VARSIZE(rdopts, offset);
> -}
> +    optionsSpecSetAddBool(view_relopt_spec_set, "security_barrier",
> +                              "View acts as a row security barrier",
> +                              AccessExclusiveLock,
> +                      0, offsetof(ViewOptions, security_barrier), false);
>  
> +    optionsSpecSetAddEnum(view_relopt_spec_set, "check_option",
> +                           "View has WITH CHECK OPTION defined (local or cascaded)",
> +                              AccessExclusiveLock, 0,
> +                              offsetof(ViewOptions, check_option),
> +                              viewCheckOptValues,
> +                              VIEW_OPTION_CHECK_OPTION_NOT_SET,
> +                              gettext_noop("Valid values are \"local\" and \"cascaded\"."));
>  
> -/*
> - * 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));
> +    return view_relopt_spec_set;
>  }
>  
>  /*
> - * build_reloptions
> - *
> - * Parses "reloptions" provided by the caller, returning them in a
> - * structure containing the parsed options.  The parsing is done with
> - * the help of a parsing table describing the allowed options, defined
> - * by "relopt_elems" of length "num_relopt_elems".
> - *
> - * "validate" must be true if reloptions value is freshly built by
> - * transformRelOptions(), as opposed to being read from the catalog, in which
> - * case the values contained in it must already be valid.
> - *
> - * NULL is returned if the passed-in options did not match any of the options
> - * in the parsing table, unless validate is true in which case an error would
> - * be reported.
> + * get_attribute_options_spec_set
> + *        Returns an options spec det for heap attributes
>   */
> -void *
> -build_reloptions(Datum reloptions, bool validate,
> -                 relopt_kind kind,
> -                 Size relopt_struct_size,
> -                 const relopt_parse_elt *relopt_elems,
> -                 int num_relopt_elems)
> -{
> -    int            numoptions;
> -    relopt_value *options;
> -    void       *rdopts;
> -
> -    /* parse options specific to given relation option kind */
> -    options = parseRelOptions(reloptions, validate, kind, &numoptions);
> -    Assert(numoptions <= num_relopt_elems);
> -
> -    /* if none set, we're done */
> -    if (numoptions == 0)
> -    {
> -        Assert(options == NULL);
> -        return NULL;
> -    }
> -
> -    /* 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);
> +static options_spec_set *attribute_options_spec_set = NULL;
>  
> -    pfree(options);
> -
> -    return rdopts;
> -}
> -
> -/*
> - * 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.
> - */
> -void *
> -build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
> +options_spec_set *
> +get_attribute_options_spec_set(void)
>  {
> -    int            noptions = list_length(relopts->options);
> -    relopt_parse_elt *elems = palloc(sizeof(*elems) * noptions);
> -    relopt_value *vals;
> -    void       *opts;
> -    int            i = 0;
> -    ListCell   *lc;
> +    if (attribute_options_spec_set)
> +            return attribute_options_spec_set;
>  
> -    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;
> -
> -        i++;
> -    }
> +    attribute_options_spec_set = allocateOptionsSpecSet(NULL,
> +                                               sizeof(AttributeOpts), 2);
>  
> -    vals = parseLocalRelOptions(relopts, options, validate);
> -    opts = allocateReloptStruct(relopts->relopt_struct_size, vals, noptions);
> -    fillRelOptions(opts, relopts->relopt_struct_size, vals, noptions, validate,
> -                   elems, noptions);
> +    optionsSpecSetAddReal(attribute_options_spec_set, "n_distinct",
> +                          "Sets the planner's estimate of the number of distinct values appearing in a column
(excludingchild relations).",
 
> +                          ShareUpdateExclusiveLock,
> +               0, offsetof(AttributeOpts, n_distinct), 0, -1.0, DBL_MAX);
>  
> -    foreach(lc, relopts->validators)
> -        ((relopts_validator) lfirst(lc)) (opts, vals, noptions);
> -
> -    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
(includingchild relations).",
 
> +                          ShareUpdateExclusiveLock,
> +     0, offsetof(AttributeOpts, n_distinct_inherited), 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)},
> -        {"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;
> -    }
> -}
> -
> -
> -/*
> - * 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);
> +        tablespace_options_spec_set = allocateOptionsSpecSet(NULL,
> +                                                  sizeof(TableSpaceOpts), 4);
>  
> -    /* Assume function is strict */
> -    if (!PointerIsValid(DatumGetPointer(reloptions)))
> -        return NULL;
> +        optionsSpecSetAddReal(tablespace_options_spec_set,
> +                                  "random_page_cost",
> +                                  "Sets the planner's estimate of the cost of a nonsequentially fetched disk page",
> +                                  ShareUpdateExclusiveLock,
> +            0, offsetof(TableSpaceOpts, random_page_cost), -1, 0.0, DBL_MAX);
>  
> -    return amoptions(reloptions, validate);
> -}
> +        optionsSpecSetAddReal(tablespace_options_spec_set, "seq_page_cost",
> +                                  "Sets the planner's estimate of the cost of a sequentially fetched disk page",
> +                                  ShareUpdateExclusiveLock,
> +               0, offsetof(TableSpaceOpts, seq_page_cost), -1, 0.0, DBL_MAX);
>  
> -/*
> - * Option parser for attribute reloptions
> - */
> -bytea *
> -attribute_reloptions(Datum reloptions, bool validate)
> -{
> -    static const relopt_parse_elt tab[] = {
> -        {"n_distinct", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct)},
> -        {"n_distinct_inherited", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct_inherited)}
> -    };
> -
> -    return (bytea *) build_reloptions(reloptions, validate,
> -                                      RELOPT_KIND_ATTRIBUTE,
> -                                      sizeof(AttributeOpts),
> -                                      tab, lengthof(tab));
> -}
> +        optionsSpecSetAddInt(tablespace_options_spec_set,
> +                                 "effective_io_concurrency",
> +                                 "Number of simultaneous requests that can be handled efficiently by the disk
subsystem",
> +                                 ShareUpdateExclusiveLock,
> +                       0, offsetof(TableSpaceOpts, effective_io_concurrency),
> +#ifdef USE_PREFETCH
> +                                 -1, 0, MAX_IO_CONCURRENCY
> +#else
> +                                 0, 0, 0
> +#endif
> +            );
>  
> -/*
> - * Option parser for tablespace reloptions
> - */
> -bytea *
> -tablespace_reloptions(Datum reloptions, bool validate)
> -{
> -    static const relopt_parse_elt tab[] = {
> -        {"random_page_cost", RELOPT_TYPE_REAL, offsetof(TableSpaceOpts, random_page_cost)},
> -        {"seq_page_cost", RELOPT_TYPE_REAL, offsetof(TableSpaceOpts, seq_page_cost)},
> -        {"effective_io_concurrency", RELOPT_TYPE_INT, offsetof(TableSpaceOpts, effective_io_concurrency)},
> -        {"maintenance_io_concurrency", RELOPT_TYPE_INT, offsetof(TableSpaceOpts, maintenance_io_concurrency)}
> -    };
> -
> -    return (bytea *) build_reloptions(reloptions, validate,
> -                                      RELOPT_KIND_TABLESPACE,
> -                                      sizeof(TableSpaceOpts),
> -                                      tab, lengthof(tab));
> +        optionsSpecSetAddInt(tablespace_options_spec_set,
> +                                 "maintenance_io_concurrency",
> +                                 "Number of simultaneous requests that can be handled efficiently by the disk
subsystemfor maintenance work.",
 
> +                                 ShareUpdateExclusiveLock,
> +                       0, offsetof(TableSpaceOpts, maintenance_io_concurrency),
> +#ifdef USE_PREFETCH
> +                                 -1, 0, MAX_IO_CONCURRENCY
> +#else
> +                                 0, 0, 0
> +#endif
> +            );
> +    }
> +    return tablespace_options_spec_set;
>  }
>  
>  /*
> @@ -2099,33 +612,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;
> -}
> +}
> \ No newline at end of file
> diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c
> index 0e8672c..0cbffad 100644
> --- a/src/backend/access/gin/gininsert.c
> +++ b/src/backend/access/gin/gininsert.c
> @@ -512,6 +512,8 @@ gininsert(Relation index, Datum *values, bool *isnull,
>  
>      oldCtx = MemoryContextSwitchTo(insertCtx);
>  
> +// elog(WARNING, "GinGetUseFastUpdate = %i", GinGetUseFastUpdate(index));
> +
>      if (GinGetUseFastUpdate(index))
>      {
>          GinTupleCollector collector;
> diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
> index 6d2d71b..d1fa3a0 100644
> --- a/src/backend/access/gin/ginutil.c
> +++ b/src/backend/access/gin/ginutil.c
> @@ -16,7 +16,7 @@
>  
>  #include "access/gin_private.h"
>  #include "access/ginxlog.h"
> -#include "access/reloptions.h"
> +#include "access/options.h"
>  #include "access/xloginsert.h"
>  #include "catalog/pg_collation.h"
>  #include "catalog/pg_type.h"
> @@ -28,6 +28,7 @@
>  #include "utils/builtins.h"
>  #include "utils/index_selfuncs.h"
>  #include "utils/typcache.h"
> +#include "utils/guc.h"
>  
>  
>  /*
> @@ -67,7 +68,6 @@ ginhandler(PG_FUNCTION_ARGS)
>      amroutine->amvacuumcleanup = ginvacuumcleanup;
>      amroutine->amcanreturn = NULL;
>      amroutine->amcostestimate = gincostestimate;
> -    amroutine->amoptions = ginoptions;
>      amroutine->amproperty = NULL;
>      amroutine->ambuildphasename = NULL;
>      amroutine->amvalidate = ginvalidate;
> @@ -82,6 +82,7 @@ ginhandler(PG_FUNCTION_ARGS)
>      amroutine->amestimateparallelscan = NULL;
>      amroutine->aminitparallelscan = NULL;
>      amroutine->amparallelrescan = NULL;
> +    amroutine->amreloptspecset = gingetreloptspecset;
>  
>      PG_RETURN_POINTER(amroutine);
>  }
> @@ -604,6 +605,7 @@ ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
>      return entries;
>  }
>  
> +/*
>  bytea *
>  ginoptions(Datum reloptions, bool validate)
>  {
> @@ -618,6 +620,7 @@ ginoptions(Datum reloptions, bool validate)
>                                        sizeof(GinOptions),
>                                        tab, lengthof(tab));
>  }
> +*/
>  
>  /*
>   * Fetch index's statistical data into *stats
> @@ -705,3 +708,31 @@ 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), 2);
> +
> +    optionsSpecSetAddBool(gin_relopt_specset, "fastupdate",
> +                        "Enables \"fast update\" feature for this GIN index",
> +                              AccessExclusiveLock,
> +                              0,
> +                              offsetof(GinOptions, useFastUpdate),
> +                              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,
> +                             0,
> +                             offsetof(GinOptions, pendingListCleanupSize),
> +                             -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 0683f42..cbbc6a5 100644
> --- a/src/backend/access/gist/gist.c
> +++ b/src/backend/access/gist/gist.c
> @@ -88,7 +88,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;
> @@ -103,6 +102,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/gistbuild.c b/src/backend/access/gist/gistbuild.c
> index baad28c..931d249 100644
> --- a/src/backend/access/gist/gistbuild.c
> +++ b/src/backend/access/gist/gistbuild.c
> @@ -215,6 +215,7 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
>              buildstate.buildMode = GIST_BUFFERING_DISABLED;
>          else                    /* must be "auto" */
>              buildstate.buildMode = GIST_BUFFERING_AUTO;
> +//elog(WARNING, "biffering_mode = %i", options->buffering_mode);
>      }
>      else
>      {
> diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
> index 43ba03b..0391915 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 "storage/indexfsm.h"
>  #include "storage/lmgr.h"
> @@ -916,20 +916,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.
>   *
> @@ -1064,3 +1050,42 @@ 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), 2);
> +
> +    optionsSpecSetAddInt(gist_relopt_specset, "fillfactor",
> +                        "Packs gist index pages only to this percentage",
> +                             NoLock,        /* No ALTER, no lock */
> +                             0,
> +                             offsetof(GiSTOptions, fillfactor),
> +                             GIST_DEFAULT_FILLFACTOR,
> +                             GIST_MIN_FILLFACTOR, 100);
> +
> +    optionsSpecSetAddEnum(gist_relopt_specset, "buffering",
> +                           "Enables buffering build for this GiST index",
> +                              NoLock,        /* No ALTER, no lock */
> +                              0,
> +                              offsetof(GiSTOptions, buffering_mode),
> +                              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 eb38104..8dc4ca7 100644
> --- a/src/backend/access/hash/hash.c
> +++ b/src/backend/access/hash/hash.c
> @@ -85,7 +85,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;
> @@ -100,6 +99,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/hashpage.c b/src/backend/access/hash/hashpage.c
> index 159646c..38f64ef 100644
> --- a/src/backend/access/hash/hashpage.c
> +++ b/src/backend/access/hash/hashpage.c
> @@ -359,6 +359,8 @@ _hash_init(Relation rel, double num_tuples, ForkNumber forkNum)
>      data_width = sizeof(uint32);
>      item_width = MAXALIGN(sizeof(IndexTupleData)) + MAXALIGN(data_width) +
>          sizeof(ItemIdData);        /* include the line pointer */
> +//elog(WARNING, "fillfactor = %i", HashGetFillFactor(rel));
> +
>      ffactor = HashGetTargetPageUsage(rel) / item_width;
>      /* keep to a sane range */
>      if (ffactor < 10)
> diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c
> index 5198728..826beab 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,24 @@ _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), 1);
> +    optionsSpecSetAddInt(hash_relopt_specset, "fillfactor",
> +                        "Packs hash index pages only to this percentage",
> +                             NoLock,        /* No ALTER -- no lock */
> +                             0,
> +                             offsetof(HashOptions, fillfactor),
> +                             HASH_DEFAULT_FILLFACTOR,
> +                             HASH_MIN_FILLFACTOR, 100);
> +
> +    return hash_relopt_specset;
> +}
> diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
> index 7355e1d..f7b117e 100644
> --- a/src/backend/access/nbtree/nbtinsert.c
> +++ b/src/backend/access/nbtree/nbtinsert.c
> @@ -2745,6 +2745,8 @@ _bt_delete_or_dedup_one_page(Relation rel, Relation heapRel,
>          _bt_bottomupdel_pass(rel, buffer, heapRel, insertstate->itemsz))
>          return;
>  
> +// elog(WARNING, "Deduplicate_items = %i", BTGetDeduplicateItems(rel));
> +
>      /* Perform deduplication pass (when enabled and index-is-allequalimage) */
>      if (BTGetDeduplicateItems(rel) && itup_key->allequalimage)
>          _bt_dedup_pass(rel, buffer, heapRel, insertstate->itup,
> diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
> index 40ad095..f171c54 100644
> --- a/src/backend/access/nbtree/nbtree.c
> +++ b/src/backend/access/nbtree/nbtree.c
> @@ -22,6 +22,7 @@
>  #include "access/nbtxlog.h"
>  #include "access/relscan.h"
>  #include "access/xlog.h"
> +#include "access/options.h"
>  #include "commands/progress.h"
>  #include "commands/vacuum.h"
>  #include "miscadmin.h"
> @@ -124,7 +125,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;
> @@ -139,6 +139,7 @@ bthandler(PG_FUNCTION_ARGS)
>      amroutine->amestimateparallelscan = btestimateparallelscan;
>      amroutine->aminitparallelscan = btinitparallelscan;
>      amroutine->amparallelrescan = btparallelrescan;
> +    amroutine->amreloptspecset = btgetreloptspecset;
>  
>      PG_RETURN_POINTER(amroutine);
>  }
> @@ -1418,3 +1419,37 @@ 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), 3);
> +
> +    optionsSpecSetAddInt(
> +        bt_relopt_specset, "fillfactor",
> +        "Packs btree index pages only to this percentage",
> +        ShareUpdateExclusiveLock, /* since it applies only to later inserts */
> +        0, offsetof(BTOptions, fillfactor),
> +        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,
> +        0, offsetof(BTOptions,vacuum_cleanup_index_scale_factor),
> +        -1, 0.0, 1e10
> +    );
> +    optionsSpecSetAddBool(
> +        bt_relopt_specset, "deduplicate_items",
> +        "Enables \"deduplicate items\" feature for this btree index",
> +        ShareUpdateExclusiveLock, /* since it applies only to later inserts */
> +        0, offsetof(BTOptions,deduplicate_items), true
> +    );
> +    return bt_relopt_specset;
> +}
> diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
> index c72b456..2588a30 100644
> --- a/src/backend/access/nbtree/nbtutils.c
> +++ b/src/backend/access/nbtree/nbtutils.c
> @@ -18,7 +18,7 @@
>  #include <time.h>
>  
>  #include "access/nbtree.h"
> -#include "access/reloptions.h"
> +#include "storage/lock.h"
>  #include "access/relscan.h"
>  #include "catalog/catalog.h"
>  #include "commands/progress.h"
> @@ -2100,25 +2100,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 03a9cd3..14429ad 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"
> @@ -72,7 +72,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;
> @@ -87,6 +86,7 @@ spghandler(PG_FUNCTION_ARGS)
>      amroutine->amestimateparallelscan = NULL;
>      amroutine->aminitparallelscan = NULL;
>      amroutine->amparallelrescan = NULL;
> +    amroutine->amreloptspecset = spggetreloptspecset;
>  
>      PG_RETURN_POINTER(amroutine);
>  }
> @@ -550,6 +550,7 @@ SpGistGetBuffer(Relation index, int flags, int needSpace, bool *isNew)
>       * related to the ones already on it.  But fillfactor mustn't cause an
>       * error for requests that would otherwise be legal.
>       */
> +//elog(WARNING, "fillfactor = %i", SpGistGetFillFactor(index));
>      needSpace += SpGistGetTargetPageFreeSpace(index);
>      needSpace = Min(needSpace, SPGIST_PAGE_CAPACITY);
>  
> @@ -721,23 +722,6 @@ SpGistInitMetapage(Page 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).
>   * Note the result is already rounded up to a MAXALIGN boundary.
> @@ -1336,3 +1320,25 @@ spgproperty(Oid index_oid, int attno,
>  
>      return true;
>  }
> +
> +static options_spec_set *spgist_relopt_specset = NULL;
> +
> +void *
> +spggetreloptspecset(void)
> +{
> +    if (!spgist_relopt_specset)
> +    {
> +        spgist_relopt_specset = allocateOptionsSpecSet(NULL,
> +                                                sizeof(SpGistOptions), 1);
> +
> +        optionsSpecSetAddInt(spgist_relopt_specset, "fillfactor",
> +                          "Packs spgist index pages only to this percentage",
> +                                 ShareUpdateExclusiveLock,        /* since it applies only
> +                                                                 * to later inserts */
> +                                 0,
> +                                 offsetof(SpGistOptions, fillfactor),
> +                                 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 0982851..4f3dbb8 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 = transformOptions(get_toast_relopt_spec_set(), (Datum) 0,
> +                                     toastDefList, 0);
>      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 146fa57..758ca34 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 c14ca27..96d465a 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,19 @@ 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 (amreloptspecsetfn)
> +    {
> +        reloptions = transformOptions(amreloptspecsetfn(),
> +                                      (Datum) 0, stmt->options, 0);
> +    }
> +    else
> +    {
> +        ereport(ERROR,
> +                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
> +                 errmsg("access method %s does not support options",
> +                        accessMethodName)));
> +    }
>  
>      /*
>       * Prepare arguments for index_create, primarily an IndexInfo structure.
> @@ -1986,8 +1996,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 1c2ebe1..7f3004f 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"
> @@ -641,7 +642,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;
> @@ -789,19 +789,37 @@ 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 = transformOptions(
> +                                      get_view_relopt_spec_set(),
> +                                      (Datum) 0, stmt->options, 0);
>              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 = transformOptions(get_partitioned_relopt_spec_set(),
> +                                      (Datum) 0, heapDefList, 0);
>              break;
> +        }
>          default:
> -            (void) heap_reloptions(relkind, 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 = transformOptions(get_heap_relopt_spec_set(),
> +                                      (Datum) 0, heapDefList, 0);
> +        }
>      }
>  
>      if (stmt->ofTypename)
> @@ -4022,7 +4040,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);
>  
> @@ -4064,7 +4082,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.
> @@ -4285,9 +4303,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;
> @@ -8062,11 +8084,11 @@ 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);
> +
> +    newOptions = transformOptions(get_attribute_options_spec_set(),
> +                                  isnull ? (Datum) 0 : datum,
> +                      castNode(List, options), OPTIONS_PARSE_MODE_FOR_ALTER |
> +                               (isReset ? OPTIONS_PARSE_MODE_FOR_RESET : 0));
>  
>      /* Build new tuple. */
>      memset(repl_null, false, sizeof(repl_null));
> @@ -13704,7 +13726,8 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
>      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       *toastDefList;
> +    options_parse_mode parse_mode;
>  
>      if (defList == NIL && operation != AT_ReplaceRelOptions)
>          return;                    /* nothing to do */
> @@ -13734,27 +13757,68 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
>      }
>  
>      /* Generate new proposed reloptions (text array) */
> -    newOptions = transformRelOptions(isnull ? (Datum) 0 : datum,
> -                                     defList, NULL, validnsps, false,
> -                                     operation == AT_ResetRelOptions);
>  
>      /* Validate */
> +    parse_mode = OPTIONS_PARSE_MODE_FOR_ALTER;
> +    if (operation == AT_ResetRelOptions)
> +        parse_mode |= OPTIONS_PARSE_MODE_FOR_RESET;
> +
>      switch (rel->rd_rel->relkind)
>      {
>          case RELKIND_RELATION:
> -        case RELKIND_TOASTVALUE:
> +        case RELKIND_TOASTVALUE: // FIXME why it is here???
>          case RELKIND_MATVIEW:
> -            (void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
> +            {
> +                char       *namespaces[] = HEAP_RELOPT_NAMESPACES;
> +                List       *heapDefList;
> +
> +                optionsDefListValdateNamespaces(defList, namespaces);
> +                heapDefList = optionsDefListFilterNamespaces(
> +                                                             defList, NULL);
> +                newOptions = transformOptions(get_heap_relopt_spec_set(),
> +                                              isnull ? (Datum) 0 : datum,
> +                                              heapDefList, parse_mode);
> +            }
>              break;
> +
>          case RELKIND_PARTITIONED_TABLE:
> -            (void) partitioned_table_reloptions(newOptions, true);
> -            break;
> +            {
> +                char       *namespaces[] = HEAP_RELOPT_NAMESPACES;
> +                List       *heapDefList;
> +
> +                optionsDefListValdateNamespaces(defList, namespaces);
> +                heapDefList = optionsDefListFilterNamespaces(
> +                                                             defList, NULL);
> +                newOptions = transformOptions(get_partitioned_relopt_spec_set(),
> +                                              isnull ? (Datum) 0 : datum,
> +                                              heapDefList, parse_mode);
> +                break;
> +            }
>          case RELKIND_VIEW:
> -            (void) view_reloptions(newOptions, true);
> -            break;
> +            {
> +
> +                newOptions = transformOptions(
> +                                      get_view_relopt_spec_set(),
> +                                      datum, defList, parse_mode);
> +                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;
> +            }
> +            parse_mode = OPTIONS_PARSE_MODE_FOR_ALTER;
> +            if (operation == AT_ResetRelOptions)
> +                parse_mode |= OPTIONS_PARSE_MODE_FOR_RESET;
> +            newOptions = transformOptions(
> +                                    rel->rd_indam->amreloptspecset(),
> +                                            isnull ? (Datum) 0 : datum,
> +                                            defList, parse_mode);
>              break;
>          default:
>              ereport(ERROR,
> @@ -13769,7 +13833,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
>      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;
>  
> @@ -13853,11 +13917,15 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
>                                      &isnull);
>          }
>  
> -        newOptions = transformRelOptions(isnull ? (Datum) 0 : datum,
> -                                         defList, "toast", validnsps, false,
> -                                         operation == AT_ResetRelOptions);
> +        parse_mode = OPTIONS_PARSE_MODE_FOR_ALTER;
> +        if (operation == AT_ResetRelOptions)
> +            parse_mode |= OPTIONS_PARSE_MODE_FOR_RESET;
> +
> +        toastDefList = optionsDefListFilterNamespaces(defList, "toast");
>  
> -        (void) heap_reloptions(RELKIND_TOASTVALUE, newOptions, true);
> +        newOptions = transformOptions(get_toast_relopt_spec_set(),
> +                                      isnull ? (Datum) 0 : datum,
> +                                      toastDefList, parse_mode);
>  
>          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 4b96eec..912699b 100644
> --- a/src/backend/commands/tablespace.c
> +++ b/src/backend/commands/tablespace.c
> @@ -345,10 +345,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 = transformOptions(get_tablespace_options_spec_set(),
> +                                                (Datum) 0, stmt->options, 0);
> +
>      if (newOptions != (Datum) 0)
>          values[Anum_pg_tablespace_spcoptions - 1] = newOptions;
>      else
> @@ -1053,10 +1052,11 @@ 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);
> +    newOptions = transformOptions(get_tablespace_options_spec_set(),
> +                                  isnull ? (Datum) 0 : datum,
> +                                  stmt->options,
> +                                  OPTIONS_PARSE_MODE_FOR_ALTER |
> +                         (stmt->isReset ? OPTIONS_PARSE_MODE_FOR_RESET : 0));
>  
>      /* Build new tuple. */
>      memset(repl_null, false, sizeof(repl_null));
> diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
> index 5564dc3..0370be7 100644
> --- a/src/backend/foreign/foreign.c
> +++ b/src/backend/foreign/foreign.c
> @@ -78,7 +78,7 @@ GetForeignDataWrapperExtended(Oid fdwid, bits16 flags)
>      if (isnull)
>          fdw->options = NIL;
>      else
> -        fdw->options = untransformRelOptions(datum);
> +        fdw->options = optionsTextArrayToDefList(datum);
>  
>      ReleaseSysCache(tp);
>  
> @@ -165,7 +165,7 @@ GetForeignServerExtended(Oid serverid, bits16 flags)
>      if (isnull)
>          server->options = NIL;
>      else
> -        server->options = untransformRelOptions(datum);
> +        server->options = optionsTextArrayToDefList(datum);
>  
>      ReleaseSysCache(tp);
>  
> @@ -233,7 +233,7 @@ GetUserMapping(Oid userid, Oid serverid)
>      if (isnull)
>          um->options = NIL;
>      else
> -        um->options = untransformRelOptions(datum);
> +        um->options = optionsTextArrayToDefList(datum);
>  
>      ReleaseSysCache(tp);
>  
> @@ -270,7 +270,7 @@ GetForeignTable(Oid relid)
>      if (isnull)
>          ft->options = NIL;
>      else
> -        ft->options = untransformRelOptions(datum);
> +        ft->options = optionsTextArrayToDefList(datum);
>  
>      ReleaseSysCache(tp);
>  
> @@ -303,7 +303,7 @@ GetForeignColumnOptions(Oid relid, AttrNumber attnum)
>      if (isnull)
>          options = NIL;
>      else
> -        options = untransformRelOptions(datum);
> +        options = optionsTextArrayToDefList(datum);
>  
>      ReleaseSysCache(tp);
>  
> @@ -572,7 +572,7 @@ pg_options_to_table(PG_FUNCTION_ARGS)
>      Datum        array = PG_GETARG_DATUM(0);
>  
>      deflist_to_tuplestore((ReturnSetInfo *) fcinfo->resultinfo,
> -                          untransformRelOptions(array));
> +                          optionsTextArrayToDefList(array));
>  
>      return (Datum) 0;
>  }
> @@ -643,7 +643,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 313d7b6..1fe41b4 100644
> --- a/src/backend/parser/parse_utilcmd.c
> +++ b/src/backend/parser/parse_utilcmd.c
> @@ -1757,7 +1757,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;
> @@ -1821,7 +1821,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 bf085aa..d12ab1a 100644
> --- a/src/backend/tcop/utility.c
> +++ b/src/backend/tcop/utility.c
> @@ -1155,6 +1155,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;
> @@ -1178,15 +1179,17 @@ 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 = transformOptions(
> +                                       get_toast_relopt_spec_set(), (Datum) 0,
> +                                                             toastDefList, 0);
>  
>                              NewRelationCreateToastTable(address.objectId,
>                                                          toast_options);
> @@ -1295,9 +1298,12 @@ ProcessUtilitySlow(ParseState *pstate,
>                       * lock on (for example) a relation on which we have no
>                       * permissions.
>                       */
> -                    lockmode = AlterTableGetLockLevel(atstmt->cmds);
> -                    relid = AlterTableLookupRelation(atstmt, lockmode);
> -
> +                    relid = AlterTableLookupRelation(atstmt, 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 72d89cb..f651129 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 13d9994..f22c2d9 100644
> --- a/src/backend/utils/cache/relcache.c
> +++ b/src/backend/utils/cache/relcache.c
> @@ -441,7 +441,7 @@ static void
>  RelationParseRelOptions(Relation relation, HeapTuple tuple)
>  {
>      bytea       *options;
> -    amoptions_function amoptsfn;
> +    amreloptspecset_function amoptspecsetfn;
>  
>      relation->rd_options = NULL;
>  
> @@ -456,11 +456,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;
> @@ -471,7 +471,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 5870f43..87f2fa5 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 d357ebb..b8fb6b9 100644
> --- a/src/include/access/amapi.h
> +++ b/src/include/access/amapi.h
> @@ -136,10 +136,6 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root,
>                                           double *indexCorrelation,
>                                           double *indexPages);
>  
> -/* parse index reloptions */
> -typedef bytea *(*amoptions_function) (Datum reloptions,
> -                                      bool validate);
> -
>  /* report AM, index, or index column property */
>  typedef bool (*amproperty_function) (Oid index_oid, int attno,
>                                       IndexAMProperty prop, const char *propname,
> @@ -186,6 +182,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan);
>  /* restore marked scan position */
>  typedef void (*amrestrpos_function) (IndexScanDesc scan);
>  
> +/* get catalog of reloptions definitions */
> +typedef void *(*amreloptspecset_function) ();
> +
>  /*
>   * Callback function signatures - for parallel index scans.
>   */
> @@ -263,7 +262,6 @@ typedef struct IndexAmRoutine
>      amvacuumcleanup_function amvacuumcleanup;
>      amcanreturn_function amcanreturn;    /* can be NULL */
>      amcostestimate_function amcostestimate;
> -    amoptions_function amoptions;
>      amproperty_function amproperty; /* can be NULL */
>      ambuildphasename_function ambuildphasename; /* can be NULL */
>      amvalidate_function amvalidate;
> @@ -275,6 +273,7 @@ typedef struct IndexAmRoutine
>      amendscan_function amendscan;
>      ammarkpos_function ammarkpos;    /* can be NULL */
>      amrestrpos_function amrestrpos; /* can be NULL */
> +    amreloptspecset_function amreloptspecset; /* can be NULL */
>  
>      /* interface functions to support parallel index scans */
>      amestimateparallelscan_function amestimateparallelscan; /* can be NULL */
> diff --git a/src/include/access/brin.h b/src/include/access/brin.h
> index 4e2be13..25b3456 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 79440eb..a798a96 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 670a40b..2b7c25c 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 553d364..015b75a 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/reloptions.h" //FIXME! should be replaced with options.h finally
>  
>  /*
>   * 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 1cce865..91922ef 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 30a216e..1fcb5f5 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 0000000..34e2917
> --- /dev/null
> +++ b/src/include/access/options.h
> @@ -0,0 +1,245 @@
> +/*-------------------------------------------------------------------------
> + *
> + * options.h
> + *      Core support for relation and tablespace options (pg_class.reloptions
> + *      and pg_tablespace.spcoptions)
> + *
> + * 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-2016, 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;
> +
> +/* flags for reloptinon definition */
> +typedef enum option_spec_flags
> +{
> +    OPTION_DEFINITION_FLAG_FORBID_ALTER = (1 << 0),        /* Altering this option
> +                                                         * is forbidden */
> +    OPTION_DEFINITION_FLAG_IGNORE = (1 << 1),    /* Skip this option while
> +                                                 * parsing. Used for WITH OIDS
> +                                                 * special case */
> +    OPTION_DEFINITION_FLAG_REJECT = (1 << 2)    /* Option will be rejected
> +                                                 * when comes from syntax
> +                                                 * analyzer, but still have
> +                                                 * default value and offset */
> +} option_spec_flags;
> +
> +/* flags that tells reloption parser how to parse*/
> +typedef enum options_parse_mode
> +{
> +    OPTIONS_PARSE_MODE_VALIDATE = (1 << 0),
> +    OPTIONS_PARSE_MODE_FOR_ALTER = (1 << 1),
> +    OPTIONS_PARSE_MODE_FOR_RESET = (1 << 2)
> +} options_parse_mode;
> +
> +
> +
> +/*
> + * 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;
> +
> +
> +/* 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_spec_flags flags;
> +    option_type type;
> +    int            struct_offset;    /* offset of the value in Bytea representation */
> +}    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;/* FIXME rewrite. Null terminated array of allowed values for
> +                                 * the option */
> +    int            default_val;    /* Number of item of allowed_values array */
> +    const char  *detailmsg;
> +}    option_spec_enum;
> +
> +/* validation routines for strings */
> +typedef void (*validate_string_option) (const char *value);
> +
> +/*
> + * When storing sting reloptions, we shoud 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 relptions
> + * 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;
> +    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 */
> +    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 represenation.
> +                                                         * Can be used for extra
> +                                                         * validation and so on */
> +    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 size_of_bytea, int num_items_expected);
> +extern void optionsSpecSetAddBool(options_spec_set * spec_set, const char *name,
> +                  const char *desc, LOCKMODE lockmode, option_spec_flags flags,
> +                                    int struct_offset, bool default_val);
> +extern void optionsSpecSetAddInt(options_spec_set * spec_set, const char *name,
> +                    const char *desc, LOCKMODE lockmode, option_spec_flags flags,
> +                    int struct_offset, 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, option_spec_flags flags,
> +      int struct_offset, 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, option_spec_flags flags,
> +            int struct_offset, 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, option_spec_flags flags,
> +int struct_offset, const char *default_val, validate_string_option validator);
> +
> +
> +/*
> + * 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 convertation, 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 transformOptions(options_spec_set * spec_set, Datum oldOptions,
> +                 List *defList, options_parse_mode parse_mode);
> +
> +#endif   /* OPTIONS_H */
> diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
> index 7c5fbeb..21b91df 100644
> --- a/src/include/access/reloptions.h
> +++ b/src/include/access/reloptions.h
> @@ -22,6 +22,7 @@
>  #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"
>  
> @@ -110,20 +111,10 @@ typedef struct relopt_real
>      double        max;
>  } relopt_real;
>  
> -/*
> - * relopt_enum_elt_def -- One member of the array of acceptable values
> - * of an enum reloption.
> - */
> -typedef struct relopt_enum_elt_def
> -{
> -    const char *string_val;
> -    int            symbol_val;
> -} relopt_enum_elt_def;
> -
>  typedef struct relopt_enum
>  {
>      relopt_gen    gen;
> -    relopt_enum_elt_def *members;
> +    opt_enum_elt_def *members;
>      int            default_val;
>      const char *detailmsg;
>      /* null-terminated array of members */
> @@ -167,6 +158,7 @@ typedef struct local_relopts
>      List       *options;        /* list of local_relopt definitions */
>      List       *validators;        /* list of relopts_validator callbacks */
>      Size        relopt_struct_size; /* size of parsed bytea structure */
> +    options_spec_set * spec_set; /* FIXME */
>  } local_relopts;
>  
>  /*
> @@ -179,21 +171,6 @@ typedef struct local_relopts
>      ((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 +187,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 +196,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 2eb2f42..d9a9b2d 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 40d3b71..dd9a05a 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 336549c..3f87f98 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 5365b063..80b39e8 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,85 @@ 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), 6);
> +
> +    optionsSpecSetAddInt(
> +        di_relopt_specset, "option_int",
> +        "Integer option for dummy_index_am",
> +        AccessExclusiveLock,
> +        0, offsetof(DummyIndexOptions, option_int),
> +        10, -10, 100
> +    );
> +
> +
> +    optionsSpecSetAddReal(
> +        di_relopt_specset, "option_real",
> +        "Real option for dummy_index_am",
> +        AccessExclusiveLock,
> +        0, offsetof(DummyIndexOptions, option_real),
> +        3.1415, -10, 100
> +    );
> +
> +    optionsSpecSetAddBool(
> +        di_relopt_specset, "option_bool",
> +        "Boolean option for dummy_index_am",
> +        AccessExclusiveLock,
> +        0, offsetof(DummyIndexOptions, option_bool), true
> +    );
> +
> +    optionsSpecSetAddEnum(di_relopt_specset, "option_enum",
> +        "Enum option for dummy_index_am",
> +        AccessExclusiveLock,
> +        0,
> +        offsetof(DummyIndexOptions, option_enum),
> +        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,
> +        0,
> +        offsetof(DummyIndexOptions, option_string_val_offset),
> +        "DefaultValue", &divalidate_string_option
> +    );
>  
>      /*
>       * 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,
> +        0,
> +        offsetof(DummyIndexOptions, option_string_null_offset),
> +        NULL, &divalidate_string_option
> +    );
> +
> +    return di_relopt_specset;
>  }
>  
>  
> +
>  /*
>   * Build a new index.
>   */
> @@ -219,19 +221,6 @@ dicostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
>  }
>  
>  /*
> - * 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.
>   */
>  static bool
> @@ -308,7 +297,6 @@ dihandler(PG_FUNCTION_ARGS)
>      amroutine->amvacuumcleanup = divacuumcleanup;
>      amroutine->amcanreturn = NULL;
>      amroutine->amcostestimate = dicostestimate;
> -    amroutine->amoptions = dioptions;
>      amroutine->amproperty = NULL;
>      amroutine->ambuildphasename = NULL;
>      amroutine->amvalidate = divalidate;
> @@ -322,12 +310,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();
> -}


-- 
  Bruce Momjian  <bruce@momjian.us>        https://momjian.us
  EDB                                      https://enterprisedb.com

  If only the physical world exists, free will is an illusion.




pgsql-hackers by date:

Previous
From: Robert Haas
Date:
Subject: Re: when the startup process doesn't (logging startup delays)
Next
From: Fujii Masao
Date:
Subject: Re: Allow pg_signal_backend members to use pg_log_backend_memory_stats().