diff --git a/contrib/bloom/bloom.h b/contrib/bloom/bloom.h
index 9966f3f46e..9a009bdbb3 100644
--- a/contrib/bloom/bloom.h
+++ b/contrib/bloom/bloom.h
@@ -17,6 +17,7 @@
 #include "access/generic_xlog.h"
 #include "access/itup.h"
 #include "access/xlog.h"
+#include "access/options.h"
 #include "fmgr.h"
 #include "nodes/pathnodes.h"
 
@@ -207,7 +208,8 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info,
 										   void *callback_state);
 extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info,
 											  IndexBulkDeleteResult *stats);
-extern bytea *bloptions(Datum reloptions, bool validate);
+extern void *blrelopt_specset(void);
+extern void blReloptionPostprocess(void *, bool validate);
 extern void blcostestimate(PlannerInfo *root, IndexPath *path,
 						   double loop_count, Cost *indexStartupCost,
 						   Cost *indexTotalCost, Selectivity *indexSelectivity,
diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c
index a434cf93ef..285f1a9c9c 100644
--- a/contrib/bloom/blutils.c
+++ b/contrib/bloom/blutils.c
@@ -15,7 +15,7 @@
 
 #include "access/amapi.h"
 #include "access/generic_xlog.h"
-#include "access/reloptions.h"
+#include "access/options.h"
 #include "bloom.h"
 #include "catalog/index.h"
 #include "commands/vacuum.h"
@@ -34,52 +34,12 @@
 
 PG_FUNCTION_INFO_V1(blhandler);
 
-/* Kind of relation options for bloom index */
-static relopt_kind bl_relopt_kind;
-
-/* parse table for fillRelOptions */
-static relopt_parse_elt bl_relopt_tab[INDEX_MAX_KEYS + 1];
+/* Catalog of relation options for bloom index */
+static options_spec_set *bl_relopt_specset;
 
 static int32 myRand(void);
 static void mySrand(uint32 seed);
 
-/*
- * Module initialize function: initialize info about Bloom relation options.
- *
- * Note: keep this in sync with makeDefaultBloomOptions().
- */
-void
-_PG_init(void)
-{
-	int			i;
-	char		buf[16];
-
-	bl_relopt_kind = add_reloption_kind();
-
-	/* Option for length of signature */
-	add_int_reloption(bl_relopt_kind, "length",
-					  "Length of signature in bits",
-					  DEFAULT_BLOOM_LENGTH, 1, MAX_BLOOM_LENGTH,
-					  AccessExclusiveLock);
-	bl_relopt_tab[0].optname = "length";
-	bl_relopt_tab[0].opttype = RELOPT_TYPE_INT;
-	bl_relopt_tab[0].offset = offsetof(BloomOptions, bloomLength);
-
-	/* Number of bits for each possible index column: col1, col2, ... */
-	for (i = 0; i < INDEX_MAX_KEYS; i++)
-	{
-		snprintf(buf, sizeof(buf), "col%d", i + 1);
-		add_int_reloption(bl_relopt_kind, buf,
-						  "Number of bits generated for each index column",
-						  DEFAULT_BLOOM_BITS, 1, MAX_BLOOM_BITS,
-						  AccessExclusiveLock);
-		bl_relopt_tab[i + 1].optname = MemoryContextStrdup(TopMemoryContext,
-														   buf);
-		bl_relopt_tab[i + 1].opttype = RELOPT_TYPE_INT;
-		bl_relopt_tab[i + 1].offset = offsetof(BloomOptions, bitSize[0]) + sizeof(int) * i;
-	}
-}
-
 /*
  * Construct a default set of Bloom options.
  */
@@ -135,7 +95,7 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amvacuumcleanup = blvacuumcleanup;
 	amroutine->amcanreturn = NULL;
 	amroutine->amcostestimate = blcostestimate;
-	amroutine->amoptions = bloptions;
+	amroutine->amreloptspecset = blrelopt_specset;
 	amroutine->amproperty = NULL;
 	amroutine->ambuildphasename = NULL;
 	amroutine->amvalidate = blvalidate;
@@ -154,6 +114,28 @@ blhandler(PG_FUNCTION_ARGS)
 	PG_RETURN_POINTER(amroutine);
 }
 
+void
+blReloptionPostprocess(void *data, bool validate)
+{
+	BloomOptions *opts = (BloomOptions *) data;
+	int			i;
+
+	if (validate)
+		for (i = 0; i < INDEX_MAX_KEYS; i++)
+		{
+			if (opts->bitSize[i] >= opts->bloomLength)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					   errmsg("col%i should not be grater than length", i)));
+			}
+		}
+
+	/* Convert signature length from # of bits to # to words, rounding up */
+	opts->bloomLength = (opts->bloomLength + SIGNWORDBITS - 1) / SIGNWORDBITS;
+}
+
+
 /*
  * Fill BloomState structure for particular index.
  */
@@ -474,24 +456,37 @@ BloomInitMetapage(Relation index)
 	UnlockReleaseBuffer(metaBuffer);
 }
 
-/*
- * Parse reloptions for bloom index, producing a BloomOptions struct.
- */
-bytea *
-bloptions(Datum reloptions, bool validate)
+void *
+blrelopt_specset(void)
 {
-	BloomOptions *rdopts;
+	int			i;
+	char		buf[16];
 
-	/* Parse the user-given reloptions */
-	rdopts = (BloomOptions *) build_reloptions(reloptions, validate,
-											   bl_relopt_kind,
-											   sizeof(BloomOptions),
-											   bl_relopt_tab,
-											   lengthof(bl_relopt_tab));
+	if (bl_relopt_specset)
+		return bl_relopt_specset;
 
-	/* Convert signature length from # of bits to # to words, rounding up */
-	if (rdopts)
-		rdopts->bloomLength = (rdopts->bloomLength + SIGNWORDBITS - 1) / SIGNWORDBITS;
 
-	return (bytea *) rdopts;
+	bl_relopt_specset = allocateOptionsSpecSet(NULL,
+							 sizeof(BloomOptions), false, INDEX_MAX_KEYS + 1);
+	bl_relopt_specset->postprocess_fun = blReloptionPostprocess;
+
+	optionsSpecSetAddInt(bl_relopt_specset, "length",
+							 "Length of signature in bits",
+							 NoLock,		/* No lock as far as ALTER is
+											 * not effective */
+							 offsetof(BloomOptions, bloomLength), NULL,
+							 DEFAULT_BLOOM_LENGTH, 1, MAX_BLOOM_LENGTH);
+
+	/* Number of bits for each possible index column: col1, col2, ... */
+	for (i = 0; i < INDEX_MAX_KEYS; i++)
+	{
+		snprintf(buf, 16, "col%d", i + 1);
+		optionsSpecSetAddInt(bl_relopt_specset, buf,
+							   "Number of bits for corresponding column",
+								 NoLock,	/* No lock as far as ALTER is
+											 * not effective */
+								 offsetof(BloomOptions, bitSize[i]), NULL,
+								 DEFAULT_BLOOM_BITS, 1, MAX_BLOOM_BITS);
+	}
+	return bl_relopt_specset;
 }
diff --git a/contrib/bloom/expected/bloom.out b/contrib/bloom/expected/bloom.out
index dae12a7d3e..e79456dbce 100644
--- a/contrib/bloom/expected/bloom.out
+++ b/contrib/bloom/expected/bloom.out
@@ -228,3 +228,6 @@ CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=0);
 ERROR:  value 0 out of bounds for option "length"
 CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (col1=0);
 ERROR:  value 0 out of bounds for option "col1"
+-- check post_validate for colN<lengh
+CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=10,col1=11);
+ERROR:  col0 should not be grater than length
diff --git a/contrib/bloom/sql/bloom.sql b/contrib/bloom/sql/bloom.sql
index 4733e1e705..0bfc767faf 100644
--- a/contrib/bloom/sql/bloom.sql
+++ b/contrib/bloom/sql/bloom.sql
@@ -93,3 +93,6 @@ SELECT reloptions FROM pg_class WHERE oid = 'bloomidx'::regclass;
 \set VERBOSITY terse
 CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=0);
 CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (col1=0);
+
+-- check post_validate for colN<lengh
+CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=10,col1=11);
diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index 5a37508c4b..1bfb98f54e 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 db08593d97..909b82f115 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 fc3ce6a53a..185a726783 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -73,7 +73,7 @@ PG_FUNCTION_INFO_V1(postgres_fdw_validator);
 Datum
 postgres_fdw_validator(PG_FUNCTION_ARGS)
 {
-	List	   *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
+	List	   *options_list = optionsTextArrayToDefList(PG_GETARG_DATUM(0));
 	Oid			catalog = PG_GETARG_OID(1);
 	ListCell   *cell;
 
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index ba78ecff66..7aca22e789 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -20,7 +20,6 @@
 #include "access/brin_pageops.h"
 #include "access/brin_xlog.h"
 #include "access/relation.h"
-#include "access/reloptions.h"
 #include "access/relscan.h"
 #include "access/table.h"
 #include "access/tableam.h"
@@ -40,7 +39,6 @@
 #include "utils/memutils.h"
 #include "utils/rel.h"
 
-
 /*
  * We use a BrinBuildState during initial construction of a BRIN index.
  * The running state is kept in a BrinMemTuple.
@@ -120,7 +118,6 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amvacuumcleanup = brinvacuumcleanup;
 	amroutine->amcanreturn = NULL;
 	amroutine->amcostestimate = brincostestimate;
-	amroutine->amoptions = brinoptions;
 	amroutine->amproperty = NULL;
 	amroutine->ambuildphasename = NULL;
 	amroutine->amvalidate = brinvalidate;
@@ -135,6 +132,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->amreloptspecset = bringetreloptspecset;
 
 	PG_RETURN_POINTER(amroutine);
 }
@@ -963,23 +961,6 @@ brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
 	return stats;
 }
 
-/*
- * reloptions processor for BRIN indexes
- */
-bytea *
-brinoptions(Datum reloptions, bool validate)
-{
-	static const relopt_parse_elt tab[] = {
-		{"pages_per_range", RELOPT_TYPE_INT, offsetof(BrinOptions, pagesPerRange)},
-		{"autosummarize", RELOPT_TYPE_BOOL, offsetof(BrinOptions, autosummarize)}
-	};
-
-	return (bytea *) build_reloptions(reloptions, validate,
-									  RELOPT_KIND_BRIN,
-									  sizeof(BrinOptions),
-									  tab, lengthof(tab));
-}
-
 /*
  * SQL-callable function to scan through an index and summarize all ranges
  * that are not currently summarized.
@@ -1764,3 +1745,30 @@ check_null_keys(BrinValues *bval, ScanKey *nullkeys, int nnullkeys)
 
 	return true;
 }
+
+static options_spec_set *brin_relopt_specset = NULL;
+
+void *
+bringetreloptspecset(void)
+{
+	if (brin_relopt_specset)
+		return brin_relopt_specset;
+	brin_relopt_specset = allocateOptionsSpecSet(NULL,
+											 sizeof(BrinOptions), false, 2);
+
+	optionsSpecSetAddInt(brin_relopt_specset, "pages_per_range",
+		   "Number of pages that each page range covers in a BRIN index",
+							 NoLock,		/* since ALTER is not allowed
+											 * no lock needed */
+							 offsetof(BrinOptions, pagesPerRange), NULL,
+							 BRIN_DEFAULT_PAGES_PER_RANGE,
+							 BRIN_MIN_PAGES_PER_RANGE,
+							 BRIN_MAX_PAGES_PER_RANGE);
+		optionsSpecSetAddBool(brin_relopt_specset, "autosummarize",
+					"Enables automatic summarization on this BRIN index",
+							  AccessExclusiveLock,
+							  offsetof(BrinOptions, autosummarize), NULL,
+							  false);
+	return brin_relopt_specset;
+}
+
diff --git a/src/backend/access/common/Makefile b/src/backend/access/common/Makefile
index b9aff0ccfd..78c9c5a8d2 100644
--- a/src/backend/access/common/Makefile
+++ b/src/backend/access/common/Makefile
@@ -18,6 +18,7 @@ OBJS = \
 	detoast.o \
 	heaptuple.o \
 	indextuple.o \
+	options.o \
 	printsimple.o \
 	printtup.o \
 	relation.o \
diff --git a/src/backend/access/common/options.c b/src/backend/access/common/options.c
new file mode 100644
index 0000000000..08d941c8b8
--- /dev/null
+++ b/src/backend/access/common/options.c
@@ -0,0 +1,1370 @@
+/*-------------------------------------------------------------------------
+ *
+ * options.c
+ *	  An uniform, context-free API for processing name=value options. Used
+ *	  to process relation options (reloptions), attribute options, opclass
+ *	  options, etc.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/common/options.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/options.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "nodes/makefuncs.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+#include "mb/pg_wchar.h"
+
+
+/*
+ * OPTIONS SPECIFICATION and OPTION SPECIFICATION SET
+ *
+ * Each option is defined via Option Specification object (Option Spec).
+ * Option Spec should have all information that is needed for processing
+ * (parsing, validating, converting) of a single option. Implemented via set of
+ * option_spec_* structures.
+ *
+ * A set of Option Specs (Options Spec Set), defines all options available for
+ * certain object (certain relation kind for example). It is a list of
+ * Options Specs, plus validation functions that can be used to validate whole
+ * option set, if needed. Implemented via options_spec_set structure and set of
+ * optionsSpecSetAdd* functions that are used for adding Option Specs items to
+ * a Set.
+ *
+ * NOTE: we choose therm "specification" instead of "definition" because therm
+ * "definition" is used for objects that came from syntax parser. So to avoid
+ * confusion here we have Option Specifications, and all "definitions" are from
+ * parser.
+ */
+
+/*
+ * OPTION VALUES REPRESENTATIONS
+ *
+ * Option values usually came from syntax parser in form of defList object,
+ * stored in pg_catalog as text array, and used when they are stored in memory
+ * as C-structure. These are different option values representations. Here goes
+ * brief description of all representations used in the code.
+ *
+ * Value List
+ *
+ * Value List is an internal representation that is used while converting
+ * option values between different representation. Value List item is called
+ * "parsed", when Value's value is converted to a proper data type and
+ * validated, or is called "unparsed", when Value's value is stored as raw
+ * string that was obtained from the source without any checks. In conversion
+ * function names first case is referred as Values, second case is referred as
+ * RawValues. Value List is implemented as List of option_value C-structures.
+ *
+ * defList
+ *
+ * Options in form of definition List that comes from syntax parser. (For
+ * reloptions it is a part of SQL query that goes after WITH, SET or RESET
+ * keywords). Can be converted to Value List using optionsDefListToRawValues
+ * and can be obtained from TEXT[] via optionsTextArrayToDefList functions.
+ *
+ * TEXT[]
+ *
+ * Options in form suitable for storig in TEXT[] field in DB. (E.g. reloptions
+ * are stores in pg_catalog.pg_class table in reloptions field). Can be
+ * converted to and from Value List using optionsValuesToTextArray and
+ * optionsTextArrayToRawValues functions.
+ *
+ * Bytea
+ *
+ * Option data stored in C-structure with varlena header in the beginning of
+ * the structure. This representation is used to pass option values to the core
+ * postgres. It is fast to read, it can be cached and so on. Bytea
+ * representation can be obtained from Vale List using optionsValuesToBytea
+ * function, and can't be converted back.
+ */
+
+/*
+ * OPTION STRING VALUE NOTION
+ *
+ * Important thing for bytea representation is that all data should be stored
+ * in one bytea chunk, including values of the string options. This is needed
+ * as  * bytea representation is cached, and can freed, moved or recreated again
+ * without any notion, so it can't have parts allocated separately.
+ *
+ * Thus memory chunk for Bytea option values representation is divided into two
+ * parts. First goes a C-structure that stores fixed length vales. Then goes
+ * memory area reserved for string values.
+ *
+ * For string values C-structure stores offsets (not pointers). These offsets
+ * can be used to access attached string value:
+ *
+ * String_pointer = Bytea_head_pointer + offest.
+ */
+
+static option_spec_basic *allocateOptionSpec(int type, const char *name,
+						 const char *desc, LOCKMODE lockmode,
+						 int struct_offset, bool is_local,
+						 option_value_postvalidate postvalidate_fn);
+
+static void parse_one_option(option_value * option, bool validate);
+static void *optionsAllocateBytea(options_spec_set * spec_set, List *options);
+
+
+static List * optionsDefListToRawValues(List *defList, bool is_for_reset);
+static Datum optionsValuesToTextArray(List *options_values);
+static List *optionsMergeOptionValues(List *old_options, List *new_options);
+
+/*
+ * allocateOptionsSpecSet
+ *		Creates new Option Spec Set object: Allocates memory and initializes
+ *		structure members.
+ *
+ * Spec Set items can be add via allocateOptionSpec and optionSpecSetAddItem
+ * functions or by calling directly any of optionsSpecSetAdd* function
+ * (preferable way)
+ *
+ * namespace - Spec Set can be bind to certain namespace (E.g.
+ * namespace.option=value). Options from other namespaces will be ignored while
+ * processing. If set to NULL, no namespace will be used at all.
+ *
+ * size_of_bytea - size of target structure of Bytea options representation
+ *
+ * num_items_expected - if you know expected number of Spec Set items set it
+ * here. Set to -1 in other cases. num_items_expected will be used for
+ * preallocating memory and will trigger error, if you try to add more items
+ * than you expected.
+ */
+
+options_spec_set *
+allocateOptionsSpecSet(const char *namespace, int bytea_size, bool is_local,
+													   int num_items_expected)
+{
+	MemoryContext oldcxt;
+	options_spec_set *spec_set;
+
+	if (! is_local)
+		oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+	spec_set = palloc(sizeof(options_spec_set));
+	if (namespace)
+	{
+		spec_set->namespace = palloc(strlen(namespace) + 1);
+		strcpy(spec_set->namespace, namespace);
+	}
+	else
+		spec_set->namespace = NULL;
+	if (num_items_expected > 0)
+	{
+		spec_set->num_allocated = num_items_expected;
+		spec_set->forbid_realloc = true;
+		spec_set->definitions = palloc(
+				 spec_set->num_allocated * sizeof(option_spec_basic *));
+	}
+	else
+	{
+		spec_set->num_allocated = 0;
+		spec_set->forbid_realloc = false;
+		spec_set->definitions = NULL;
+	}
+	spec_set->num = 0;
+	spec_set->struct_size = bytea_size;
+	spec_set->postprocess_fun = NULL;
+	spec_set->is_local = is_local;
+	if (! is_local)
+		MemoryContextSwitchTo(oldcxt);
+	return spec_set;
+}
+
+/*
+ * allocateOptionSpec
+ *		Allocates a new Option Specifiation object of desired type and
+ *		initialize the type-independent fields
+ */
+static option_spec_basic *
+allocateOptionSpec(int type, const char *name, const char *desc,
+					LOCKMODE lockmode, int struct_offset, bool is_local,
+					option_value_postvalidate postvalidate_fn)
+{
+	MemoryContext oldcxt;
+	size_t		size;
+	option_spec_basic *newoption;
+
+	if (! is_local)
+		oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+
+	switch (type)
+	{
+		case OPTION_TYPE_BOOL:
+			size = sizeof(option_spec_bool);
+			break;
+		case OPTION_TYPE_INT:
+			size = sizeof(option_spec_int);
+			break;
+		case OPTION_TYPE_REAL:
+			size = sizeof(option_spec_real);
+			break;
+		case OPTION_TYPE_ENUM:
+			size = sizeof(option_spec_enum);
+			break;
+		case OPTION_TYPE_STRING:
+			size = sizeof(option_spec_string);
+			break;
+		default:
+			elog(ERROR, "unsupported reloption type %d", type);
+			return NULL;		/* keep compiler quiet */
+	}
+
+	newoption = palloc(size);
+
+	newoption->name = pstrdup(name);
+	if (desc)
+		newoption->desc = pstrdup(desc);
+	else
+		newoption->desc = NULL;
+	newoption->type = type;
+	newoption->lockmode = lockmode;
+	newoption->struct_offset = struct_offset;
+	newoption->postvalidate_fn = postvalidate_fn;
+
+	if (! is_local)
+		MemoryContextSwitchTo(oldcxt);
+
+	return newoption;
+}
+
+/*
+ * optionSpecSetAddItem
+ *		Adds pre-created Option Specification objec to the Spec Set
+ */
+static void
+optionSpecSetAddItem(option_spec_basic * newoption,
+					 options_spec_set * spec_set)
+{
+	if (spec_set->num >= spec_set->num_allocated)
+	{
+		MemoryContext oldcxt;
+
+		Assert(!spec_set->forbid_realloc);
+		if (!spec_set->is_local)
+			oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+
+		if (spec_set->num_allocated == 0)
+		{
+			spec_set->num_allocated = 8;
+			spec_set->definitions = palloc(
+				 spec_set->num_allocated * sizeof(option_spec_basic *));
+		}
+		else
+		{
+			spec_set->num_allocated *= 2;
+			spec_set->definitions = repalloc(spec_set->definitions,
+				 spec_set->num_allocated * sizeof(option_spec_basic *));
+		}
+		if (!spec_set->is_local)
+			MemoryContextSwitchTo(oldcxt);
+	}
+	spec_set->definitions[spec_set->num] = newoption;
+	spec_set->num++;
+}
+
+
+/*
+ * optionsSpecSetAddBool
+ *		Adds boolean Option Specification entry to the Spec Set
+ */
+void
+optionsSpecSetAddBool(options_spec_set * spec_set, const char *name,
+					  const char *desc, LOCKMODE lockmode, int struct_offset,
+					  option_value_postvalidate postvalidate_fn,
+					  bool default_val)
+{
+	option_spec_bool *spec_set_item;
+
+	spec_set_item = (option_spec_bool *) allocateOptionSpec(OPTION_TYPE_BOOL,
+										name, desc, lockmode, struct_offset,
+										spec_set->is_local, postvalidate_fn);
+
+	spec_set_item->default_val = default_val;
+
+	optionSpecSetAddItem((option_spec_basic *) spec_set_item, spec_set);
+}
+
+/*
+ * optionsSpecSetAddInt
+ *		Adds integer Option Specification entry to the Spec Set
+ */
+void
+optionsSpecSetAddInt(options_spec_set * spec_set, const char *name,
+					 const char *desc, LOCKMODE lockmode, int struct_offset,
+					 option_value_postvalidate postvalidate_fn,
+					 int default_val, int min_val, int max_val)
+{
+	option_spec_int *spec_set_item;
+
+	spec_set_item = (option_spec_int *) allocateOptionSpec(OPTION_TYPE_INT,
+										name, desc, lockmode, struct_offset,
+										spec_set->is_local, postvalidate_fn);
+
+	spec_set_item->default_val = default_val;
+	spec_set_item->min = min_val;
+	spec_set_item->max = max_val;
+
+	optionSpecSetAddItem((option_spec_basic *) spec_set_item, spec_set);
+}
+
+/*
+ * optionsSpecSetAddReal
+ *		Adds float Option Specification entry to the Spec Set
+ */
+void
+optionsSpecSetAddReal(options_spec_set * spec_set, const char *name,
+					  const char *desc, LOCKMODE lockmode, int struct_offset,
+					  option_value_postvalidate postvalidate_fn,
+					  double default_val, double min_val, double max_val)
+{
+	option_spec_real *spec_set_item;
+
+	spec_set_item = (option_spec_real *) allocateOptionSpec(OPTION_TYPE_REAL,
+										 name, desc, lockmode, struct_offset,
+										 spec_set->is_local, postvalidate_fn);
+
+	spec_set_item->default_val = default_val;
+	spec_set_item->min = min_val;
+	spec_set_item->max = max_val;
+
+	optionSpecSetAddItem((option_spec_basic *) spec_set_item, spec_set);
+}
+
+/*
+ * optionsSpecSetAddEnum
+ *		Adds enum Option Specification entry to the Spec Set
+ *
+ * The members array must have a terminating NULL entry.
+ *
+ * The detailmsg is shown when unsupported values are passed, and has this
+ * form:   "Valid values are \"foo\", \"bar\", and \"bar\"."
+ *
+ * The members array and detailmsg are not copied -- caller must ensure that
+ * they are valid throughout the life of the process.
+ */
+
+void
+optionsSpecSetAddEnum(options_spec_set * spec_set, const char *name,
+					  const char *desc,	LOCKMODE lockmode, int struct_offset,
+					  option_value_postvalidate postvalidate_fn,
+					  opt_enum_elt_def * members, int default_val,
+					  const char *detailmsg)
+{
+	option_spec_enum *spec_set_item;
+
+	spec_set_item = (option_spec_enum *) allocateOptionSpec(OPTION_TYPE_ENUM,
+										 name, desc, lockmode, struct_offset,
+										 spec_set->is_local, postvalidate_fn);
+
+	spec_set_item->default_val = default_val;
+	spec_set_item->members = members;
+	spec_set_item->detailmsg = detailmsg;
+
+	optionSpecSetAddItem((option_spec_basic *) spec_set_item, spec_set);
+}
+
+/*
+ * optionsSpecSetAddString
+ *		Adds string Option Specification entry to the Spec Set
+ *
+ * "validator" is an optional function pointer that can be used to test the
+ * validity of the values. It must elog(ERROR) when the argument string is
+ * not acceptable for the variable. Note that the default value must pass
+ * the validation.
+ */
+void
+optionsSpecSetAddString(options_spec_set * spec_set, const char *name,
+					const char *desc, LOCKMODE lockmode, int struct_offset,
+					option_value_postvalidate postvalidate_fn,
+					const char *default_val, validate_string_option validator,
+					fill_string_option filler)
+{
+	option_spec_string *spec_set_item;
+
+	/* make sure the validator/default combination is sane */
+	if (validator)
+		(validator) (default_val);
+
+	spec_set_item = (option_spec_string *) allocateOptionSpec(
+										OPTION_TYPE_STRING,
+										name, desc, lockmode, struct_offset,
+										spec_set->is_local, postvalidate_fn);
+	spec_set_item->validate_cb = validator;
+	spec_set_item->fill_cb = filler;
+
+	if (default_val)
+		spec_set_item->default_val = MemoryContextStrdup(TopMemoryContext,
+														default_val);
+	else
+		spec_set_item->default_val = NULL;
+	optionSpecSetAddItem((option_spec_basic *) spec_set_item, spec_set);
+}
+
+/* optionsDefListToRawValues
+ *		Converts options values from DefList representation into Raw Values
+ *		List.
+ *
+ * No parsing is done here except for checking that RESET syntax is correct
+ * (i.e. does not have =name part of value=name template). Syntax analyzer does
+ * not see difference between SET and RESET cases, so we should treat it here
+ * manually
+ */
+static List *
+optionsDefListToRawValues(List *defList, bool is_for_reset)
+{
+	ListCell   *cell;
+	List	   *result = NIL;
+
+	foreach(cell, defList)
+	{
+		option_value *option_dst;
+		DefElem    *def = (DefElem *) lfirst(cell);
+		char	   *value;
+
+		option_dst = palloc(sizeof(option_value));
+
+		if (def->defnamespace)
+		{
+			option_dst->namespace = palloc(strlen(def->defnamespace) + 1);
+			strcpy(option_dst->namespace, def->defnamespace);
+		}
+		else
+		{
+			option_dst->namespace = NULL;
+		}
+		option_dst->raw_name = palloc(strlen(def->defname) + 1);
+		strcpy(option_dst->raw_name, def->defname);
+
+		if (is_for_reset)
+		{
+			/*
+			 * If this option came from RESET statement we should throw error
+			 * it it brings us name=value data, as syntax analyzer do not
+			 * prevent it
+			 */
+			if (def->arg != NULL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("RESET must not include values for parameters")));
+
+			option_dst->status = OPTION_VALUE_STATUS_FOR_RESET;
+		}
+		else
+		{
+			/*
+			 * For SET statement we should treat (name) expression as if it is
+			 * actually (name=true) so do it here manually. In other cases
+			 * just use value as we should use it
+			 */
+			option_dst->status = OPTION_VALUE_STATUS_RAW;
+			if (def->arg != NULL)
+				value = defGetString(def);
+			else
+				value = "true";
+			option_dst->raw_value = palloc(strlen(value) + 1);
+			strcpy(option_dst->raw_value, value);
+		}
+
+		result = lappend(result, option_dst);
+	}
+	return result;
+}
+
+/*
+ * optionsValuesToTextArray
+ *		Converts options Values List (option_values) into TEXT[] representation
+ *
+ * This conversion is usually needed for saving option values into database
+ * (e.g. to store reloptions in pg_class.reloptions)
+ */
+
+Datum
+optionsValuesToTextArray(List *options_values)
+{
+	ArrayBuildState *astate = NULL;
+	ListCell   *cell;
+	Datum		result;
+
+	foreach(cell, options_values)
+	{
+		option_value *option = (option_value *) lfirst(cell);
+		const char *name;
+		char	   *value;
+		text	   *t;
+		int			len;
+
+		/*
+		 * Raw value were not cleared while parsing, so instead of converting
+		 * it back, just use it to store value as text
+		 */
+		value = option->raw_value;
+
+		Assert(option->status != OPTION_VALUE_STATUS_EMPTY);
+
+		/*
+		 * Name will be taken from option definition, if option were parsed or
+		 * from raw_name if option were not parsed for some reason
+		 */
+		if (option->status == OPTION_VALUE_STATUS_PARSED)
+			name = option->gen->name;
+		else
+			name = option->raw_name;
+
+		/*
+		 * Now build "name=value" string and append it to the array
+		 */
+		len = VARHDRSZ + strlen(name) + strlen(value) + 1;
+		t = (text *) palloc(len + 1);
+		SET_VARSIZE(t, len);
+		sprintf(VARDATA(t), "%s=%s", name, value);
+		astate = accumArrayResult(astate, PointerGetDatum(t), false,
+								  TEXTOID, CurrentMemoryContext);
+	}
+	if (astate)
+		result = makeArrayResult(astate, CurrentMemoryContext);
+	else
+		result = (Datum) 0;
+
+	return result;
+}
+
+/*
+ * optionsTextArrayToRawValues
+ *		Converts option values from TEXT[] representation (datum_array) into
+ *		 Raw Values List.
+ *
+ * Used while fetching options values from DB, as a first step of converting
+ * them to other representations.
+ */
+List *
+optionsTextArrayToRawValues(Datum array_datum)
+{
+	List	   *result = NIL;
+
+	if (PointerIsValid(DatumGetPointer(array_datum)))
+	{
+		ArrayType  *array = DatumGetArrayTypeP(array_datum);
+		Datum	   *options;
+		int			noptions;
+		int			i;
+
+		deconstruct_array(array, TEXTOID, -1, false, 'i',
+						  &options, NULL, &noptions);
+
+		for (i = 0; i < noptions; i++)
+		{
+			option_value *option_dst;
+			char	   *text_str = VARDATA(options[i]);
+			int			text_len = VARSIZE(options[i]) - VARHDRSZ;
+			int			i;
+			int			name_len = -1;
+			char	   *name;
+			int			raw_value_len;
+			char	   *raw_value;
+
+			/*
+			 * Find position of '=' sign and treat id as a separator between
+			 * name and value in "name=value" item
+			 */
+			for (i = 0; i < text_len; i = i + pg_mblen(text_str))
+			{
+				if (text_str[i] == '=')
+				{
+					name_len = i;
+					break;
+				}
+			}
+			Assert(name_len >= 1);		/* Just in case */
+
+			raw_value_len = text_len - name_len - 1;
+
+			/*
+			 * Copy name from src
+			 */
+			name = palloc(name_len + 1);
+			memcpy(name, text_str, name_len);
+			name[name_len] = '\0';
+
+			/*
+			 * Copy value from src
+			 */
+			raw_value = palloc(raw_value_len + 1);
+			memcpy(raw_value, text_str + name_len + 1, raw_value_len);
+			raw_value[raw_value_len] = '\0';
+
+			/*
+			 * Create new option_value item
+			 */
+			option_dst = palloc(sizeof(option_value));
+			option_dst->status = OPTION_VALUE_STATUS_RAW;
+			option_dst->raw_name = name;
+			option_dst->raw_value = raw_value;
+			option_dst->namespace = NULL;
+
+			result = lappend(result, option_dst);
+		}
+	}
+	return result;
+}
+
+/*
+ * optionsMergeOptionValues
+ *		Updates(or Resets) values from one Options Values List(old_options),
+ *		with values from another Options Values List (new_options)
+
+ * This function is used while ALTERing options of some object.
+ * If option from new_options list has OPTION_VALUE_STATUS_FOR_RESET flag
+ * on, option with that name will be excluded result list.
+ */
+static List *
+optionsMergeOptionValues(List *old_options, List *new_options)
+{
+	List	   *result = NIL;
+	ListCell   *old_cell;
+	ListCell   *new_cell;
+
+	/*
+	 * First add to result all old options that are not mentioned in new list
+	 */
+	foreach(old_cell, old_options)
+	{
+		bool		found;
+		const char *old_name;
+		option_value *old_option;
+
+		old_option = (option_value *) lfirst(old_cell);
+		if (old_option->status == OPTION_VALUE_STATUS_PARSED)
+			old_name = old_option->gen->name;
+		else
+			old_name = old_option->raw_name;
+
+		/*
+		 * Looking for a new option with same name
+		 */
+		found = false;
+		foreach(new_cell, new_options)
+		{
+			option_value *new_option;
+			const char *new_name;
+
+			new_option = (option_value *) lfirst(new_cell);
+			if (new_option->status == OPTION_VALUE_STATUS_PARSED)
+				new_name = new_option->gen->name;
+			else
+				new_name = new_option->raw_name;
+
+			if (strcmp(new_name, old_name) == 0)
+			{
+				found = true;
+				break;
+			}
+		}
+		if (!found)
+			result = lappend(result, old_option);
+	}
+	/*
+	 * Now add all to result all new options that are not designated for reset
+	 */
+	foreach(new_cell, new_options)
+	{
+		option_value *new_option;
+		new_option = (option_value *) lfirst(new_cell);
+
+		if(new_option->status != OPTION_VALUE_STATUS_FOR_RESET)
+			result = lappend(result, new_option);
+	}
+	return result;
+}
+
+/*
+ * optionsDefListValdateNamespaces
+ *		Checks that defList has only options with namespaces from
+ *		allowed_namespaces array. Items without namespace are also accepted
+ *
+ * Used while validation of syntax parser output. Error is thrown if unproper
+ * namespace is found.
+ */
+void
+optionsDefListValdateNamespaces(List *defList, char **allowed_namespaces)
+{
+	ListCell   *cell;
+
+	foreach(cell, defList)
+	{
+		DefElem    *def = (DefElem *) lfirst(cell);
+
+		/*
+		 * Checking namespace only for options that have namespaces. Options
+		 * with no namespaces are always accepted
+		 */
+		if (def->defnamespace)
+		{
+			bool		found = false;
+			int			i = 0;
+
+			while (allowed_namespaces[i])
+			{
+				if (strcmp(def->defnamespace,
+								  allowed_namespaces[i]) == 0)
+				{
+					found = true;
+					break;
+				}
+				i++;
+			}
+			if (!found)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("unrecognized parameter namespace \"%s\"",
+								def->defnamespace)));
+		}
+	}
+}
+
+/*
+ * optionsDefListFilterNamespaces
+ *		Filter out DefList items that has "namespace" namespace. If "namespace"
+ *		is NULL, only namespaseless options are returned
+ */
+List *
+optionsDefListFilterNamespaces(List *defList, const char *namespace)
+{
+	ListCell   *cell;
+	List	   *result = NIL;
+
+	foreach(cell, defList)
+	{
+		DefElem    *def = (DefElem *) lfirst(cell);
+
+		if ((!namespace && !def->defnamespace) ||
+			(namespace && def->defnamespace &&
+			 strcmp(namespace, def->defnamespace) == 0))
+		{
+			result = lappend(result, def);
+		}
+	}
+	return result;
+}
+
+/*
+ * optionsTextArrayToDefList
+ *		Converts option values from TEXT[] representation into DefList
+ *		representation.
+ */
+List *
+optionsTextArrayToDefList(Datum options)
+{
+	List	   *result = NIL;
+	ArrayType  *array;
+	Datum	   *optiondatums;
+	int			noptions;
+	int			i;
+
+	/* Nothing to do if no options */
+	if (!PointerIsValid(DatumGetPointer(options)))
+		return result;
+
+	array = DatumGetArrayTypeP(options);
+
+	deconstruct_array(array, TEXTOID, -1, false, 'i',
+					  &optiondatums, NULL, &noptions);
+
+	for (i = 0; i < noptions; i++)
+	{
+		char	   *s;
+		char	   *p;
+		Node	   *val = NULL;
+
+		s = TextDatumGetCString(optiondatums[i]);
+		p = strchr(s, '=');
+		if (p)
+		{
+			*p++ = '\0';
+			val = (Node *) makeString(pstrdup(p));
+		}
+		result = lappend(result, makeDefElem(pstrdup(s), val, -1));
+	}
+
+	return result;
+}
+
+/*
+ * optionsDefListToTextArray
+ *		Converts option values from DefList representation into TEXT[]
+ *		representation.
+ */
+Datum
+optionsDefListToTextArray(List *defList)
+{
+	ListCell   *cell;
+	Datum		result;
+	ArrayBuildState *astate = NULL;
+
+	foreach(cell, defList)
+	{
+		DefElem	   *def = (DefElem *) lfirst(cell);
+		const char *name = def->defname;
+		const char *value;
+		text	   *t;
+		int			len;
+
+		if (def->arg != NULL)
+			value = defGetString(def);
+		else
+			value = "true";
+
+		if (def->defnamespace)
+		{
+			/*
+			 * This function is used for backward compatibility in the place
+			 * where namespases are not allowed
+			 */
+			Assert(false); /* Should not get here */
+			return (Datum) 0;
+		}
+		len = VARHDRSZ + strlen(name) + strlen(value) + 1;
+		t = (text *) palloc(len + 1);
+		SET_VARSIZE(t, len);
+		sprintf(VARDATA(t), "%s=%s", name, value);
+		astate = accumArrayResult(astate, PointerGetDatum(t), false,
+								  TEXTOID, CurrentMemoryContext);
+
+	}
+	if (astate)
+		result = makeArrayResult(astate, CurrentMemoryContext);
+	else
+		result = (Datum) 0;
+	return result;
+}
+
+
+/*
+ * optionsParseRawValues
+ *		Transforms RawValues List into [Parsed] Values List. Validation is done
+ *		if validate flag is set.
+ *
+ * Options data that come parsed SQL query or DB storage, first converted into
+ * RawValues (where value are kept in text format), then is parsed into
+ * Values using this function
+ *
+ * Validation is used only for data that came from SQL query. We trust that
+ * data that came from DB is correct.
+ *
+ * If validation is off, all unknown options are kept unprsed so they will
+ * be stored back to DB until user RESETs them directly.
+ *
+ * This function destroys incoming list.
+ */
+List *
+optionsParseRawValues(List *raw_values, options_spec_set * spec_set,
+																bool validate)
+{
+	ListCell   *cell;
+	List	   *result = NIL;
+	bool	   *is_set;
+	int			i;
+
+	is_set = palloc0(sizeof(bool) * spec_set->num);
+	foreach(cell, raw_values)
+	{
+		option_value *option = (option_value *) lfirst(cell);
+		bool		found = false;
+
+		/* option valuess with RESET status does not need parsing */
+		Assert(option->status != OPTION_VALUE_STATUS_FOR_RESET);
+
+		/* Should not parse Values Set twice*/
+		Assert(option->status != OPTION_VALUE_STATUS_PARSED);
+
+		if (validate && option->namespace && (!spec_set->namespace ||
+				  strcmp(spec_set->namespace, option->namespace) != 0))
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("unrecognized parameter namespace \"%s\"",
+							option->namespace)));
+		}
+
+		for (i = 0; i < spec_set->num; i++)
+		{
+			option_spec_basic *opt_spec = spec_set->definitions[i];
+
+			if (strcmp(option->raw_name, opt_spec->name) == 0)
+			{
+				if (validate && is_set[i])
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						  errmsg("parameter \"%s\" specified more than once",
+								 option->raw_name)));
+				}
+				pfree(option->raw_name);
+				option->raw_name = NULL;
+				option->gen = opt_spec;
+				parse_one_option(option, validate);
+				is_set[i] = true;
+				found = true;
+				break;
+			}
+		}
+		if (validate && !found)
+		{
+			if (option->namespace)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("unrecognized parameter \"%s.%s\"",
+								option->namespace, option->raw_name)));
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("unrecognized parameter \"%s\"",
+								option->raw_name)));
+		}
+		result = lappend(result, option);
+	}
+	return result;
+}
+
+/*
+ * parse_one_option
+ *		Function to parse and validate single option.
+ *
+ * See optionsParseRawValues for more info.
+ *
+ * Link to Option Spec for the option is embedded into "option_value"
+ */
+static void
+parse_one_option(option_value * option, bool validate)
+{
+	char	   *value;
+	bool		parsed;
+
+	value = option->raw_value;
+
+	switch (option->gen->type)
+	{
+		case OPTION_TYPE_BOOL:
+			{
+				parsed = parse_bool(value, &option->values.bool_val);
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("invalid value for boolean option \"%s\": %s",
+							   option->gen->name, value)));
+			}
+			break;
+		case OPTION_TYPE_INT:
+			{
+				option_spec_int *optint =
+				(option_spec_int *) option->gen;
+
+				parsed = parse_int(value, &option->values.int_val, 0, NULL);
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("invalid value for integer option \"%s\": %s",
+							   option->gen->name, value)));
+				if (validate && (option->values.int_val < optint->min ||
+								 option->values.int_val > optint->max))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						   errmsg("value %s out of bounds for option \"%s\"",
+								  value, option->gen->name),
+					 errdetail("Valid values are between \"%d\" and \"%d\".",
+							   optint->min, optint->max)));
+			}
+			break;
+		case OPTION_TYPE_REAL:
+			{
+				option_spec_real *optreal =
+				(option_spec_real *) option->gen;
+
+				parsed = parse_real(value, &option->values.real_val, 0, NULL);
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for floating point option \"%s\": %s",
+									option->gen->name, value)));
+				if (validate && (option->values.real_val < optreal->min ||
+								 option->values.real_val > optreal->max))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						   errmsg("value %s out of bounds for option \"%s\"",
+								  value, option->gen->name),
+					 errdetail("Valid values are between \"%f\" and \"%f\".",
+							   optreal->min, optreal->max)));
+			}
+			break;
+		case OPTION_TYPE_ENUM:
+			{
+				option_spec_enum *optenum =
+										(option_spec_enum *) option->gen;
+				opt_enum_elt_def *elt;
+				parsed = false;
+				for (elt = optenum->members; elt->string_val; elt++)
+				{
+					if (strcmp(value, elt->string_val) == 0)
+					{
+						option->values.enum_val = elt->symbol_val;
+						parsed = true;
+						break;
+					}
+				}
+				if (!parsed)
+				{
+					ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("invalid value for enum option \"%s\": %s",
+								option->gen->name, value),
+						 optenum->detailmsg ?
+						 errdetail_internal("%s", _(optenum->detailmsg)) : 0));
+				}
+			}
+			break;
+		case OPTION_TYPE_STRING:
+			{
+				option_spec_string *optstring =
+				(option_spec_string *) option->gen;
+
+				option->values.string_val = value;
+				if (validate && optstring->validate_cb)
+					(optstring->validate_cb) (value);
+				parsed = true;
+			}
+			break;
+		default:
+			elog(ERROR, "unsupported reloption type %d", option->gen->type);
+			parsed = true;		/* quiet compiler */
+			break;
+	}
+	if (validate && option->gen->postvalidate_fn)
+	{
+		option->gen->postvalidate_fn(option);
+	}
+
+	if (parsed)
+		option->status = OPTION_VALUE_STATUS_PARSED;
+
+}
+
+/*
+ * optionsAllocateBytea
+ *		Allocates memory for Bytea options representation
+ *
+ * We need special function for this, as string option values are embedded into
+ * Bytea object, stored at the rear part of the memory chunk. Thus we need to
+ * allocate extra memory, so all string values would fit in. This function
+ * calculates required size and do allocation.
+ *
+ * See "OPTION STRING VALUE NOTION" at the beginning of the file for better
+ * understanding.
+ */
+static void *
+optionsAllocateBytea(options_spec_set * spec_set, List *options)
+{
+	Size		size;
+	int			i;
+	ListCell   *cell;
+	int			length;
+	void	   *res;
+
+	size = spec_set->struct_size;
+
+	/* Calculate size needed to store all string values for this option */
+	for (i = 0; i < spec_set->num; i++)
+	{
+		option_spec_basic *opt_spec = spec_set->definitions[i];
+		option_spec_string *opt_spec_str;
+		bool		found = false;
+		option_value *option;
+		const char *val = NULL;
+
+		/* Not interested in non-string options, skipping */
+		if (opt_spec->type != OPTION_TYPE_STRING)
+			continue;
+		/*
+		 * Trying to find option_value that references opt_spec
+		 * entry
+		 */
+		opt_spec_str = (option_spec_string *) opt_spec;
+		foreach(cell, options)
+		{
+			option = (option_value *) lfirst(cell);
+			if (option->status == OPTION_VALUE_STATUS_PARSED &&
+				strcmp(option->gen->name, opt_spec->name) == 0)
+			{
+				found = true;
+				break;
+			}
+		}
+		if (found)
+			val = option->values.string_val;
+		else
+			val = opt_spec_str->default_val;
+
+		if (opt_spec_str->fill_cb)
+			length = opt_spec_str->fill_cb(val, NULL);
+		else
+			if (val)
+				length = strlen(val) + 1;
+			else
+				length = 0; /* "Default Value is NULL" case */
+
+		/* Add total length of each string values to basic size */
+		size += length;
+	}
+
+	res = palloc0(size);
+	SET_VARSIZE(res, size);
+	return res;
+}
+
+/*
+ * optionsValuesToBytea
+ *		Converts options values from Value List representation to Bytea
+ *		representation.
+ *
+ * Fills resulting Bytea with option values according to Option Sec Set.
+ *
+ * For understanding processing of the string option please read "OPTION STRING
+ * VALUE NOTION" at the beginning of the file.
+ */
+bytea *
+optionsValuesToBytea(List *options, options_spec_set * spec_set)
+{
+	char	   *data;
+	char	   *string_values_buffer;
+	int			i;
+
+	data = optionsAllocateBytea(spec_set, options);
+
+	/* place for string data starts right after original structure */
+	string_values_buffer = data + spec_set->struct_size;
+
+	for (i = 0; i < spec_set->num; i++)
+	{
+		option_value *found = NULL;
+		ListCell   *cell;
+		char	   *item_pos;
+		option_spec_basic *opt_spec = spec_set->definitions[i];
+
+		if (opt_spec->struct_offset < 0)
+			continue; /* This option value should not be stored in Bytea for
+					   * some reason. May be it is deprecated and has warning
+					   * or error in postvalidate function */
+
+		/* Calculate the position of the item inside the structure */
+		item_pos = data + opt_spec->struct_offset;
+
+		/* Looking for the corresponding option from options list */
+		foreach(cell, options)
+		{
+			option_value *option = (option_value *) lfirst(cell);
+
+			if (option->status == OPTION_VALUE_STATUS_RAW)
+				continue;	/* raw can come from db. Just ignore them then */
+			Assert(option->status != OPTION_VALUE_STATUS_EMPTY);
+
+			if (strcmp(opt_spec->name, option->gen->name) == 0)
+			{
+				found = option;
+				break;
+			}
+		}
+		/* writing to the proper position either option value or default val */
+		switch (opt_spec->type)
+		{
+			case OPTION_TYPE_BOOL:
+				*(bool *) item_pos = found ?
+					found->values.bool_val :
+					((option_spec_bool *) opt_spec)->default_val;
+				break;
+			case OPTION_TYPE_INT:
+				*(int *) item_pos = found ?
+					found->values.int_val :
+					((option_spec_int *) opt_spec)->default_val;
+				break;
+			case OPTION_TYPE_REAL:
+				*(double *) item_pos = found ?
+					found->values.real_val :
+					((option_spec_real *) opt_spec)->default_val;
+				break;
+			case OPTION_TYPE_ENUM:
+				*(int *) item_pos = found ?
+					found->values.enum_val :
+					((option_spec_enum *) opt_spec)->default_val;
+				break;
+
+			case OPTION_TYPE_STRING:
+				{
+					/*
+					 * For string options: writing string value at the string
+					 * buffer after the structure, and storing and offset to
+					 * that value
+					 */
+					char	   *value = NULL;
+					option_spec_string * opt_spec_str =
+											   (option_spec_string *) opt_spec;
+
+					if (found)
+						value = found->values.string_val;
+					else
+						value = opt_spec_str->default_val;
+
+					if (opt_spec_str->fill_cb)
+					{
+						Size		size =
+						opt_spec_str->fill_cb(value, string_values_buffer);
+						if (size)
+						{
+							*(int *) item_pos = string_values_buffer - data;
+							string_values_buffer += size;
+						} else
+							*(int *) item_pos =
+											OPTION_STRING_VALUE_NOT_SET_OFFSET;
+					} else
+					{
+						*(int *) item_pos = value ?
+							string_values_buffer - data :
+							OPTION_STRING_VALUE_NOT_SET_OFFSET;
+						if (value)
+						{
+							strcpy(string_values_buffer, value);
+							string_values_buffer += strlen(value) + 1;
+						}
+					}
+				}
+				break;
+			default:
+				elog(ERROR, "unsupported reloption type %d",
+					 opt_spec->type);
+				break;
+		}
+	}
+	return (void *) data;
+}
+
+/*
+ * optionDefListToTextArray
+ *		Converts options from defList to TEXT[] representation.
+ *
+ * Used when new relation (or other object) is created. defList comes from
+ * SQL syntax parser, TEXT[] goes to DB storage. Options are always validated
+ * while coversion.
+ */
+Datum
+optionDefListToTextArray(options_spec_set * spec_set, List *defList)
+{
+	Datum		result;
+	List	   *new_values;
+
+	/* Parse and validate new values */
+	new_values = optionsDefListToRawValues(defList, false);
+	new_values = optionsParseRawValues(new_values, spec_set, true);
+
+	/* Some checks can be done in postprocess_fun, we should call it */
+	if (spec_set->postprocess_fun)
+	{
+		bytea	   *data = optionsValuesToBytea(new_values, spec_set);
+
+		spec_set->postprocess_fun(data, true);
+		pfree(data);
+	}
+	result = optionsValuesToTextArray(new_values);
+	return result;
+}
+
+/*
+ * optionsUpdateTexArrayWithDefList
+ * 		Modifies oldOptions values (in TEXT[] representation) with defList
+ * 		values.
+ *
+ * Old values are appened or replaced with new values if do_reset flag is set
+ * to false. If do_reset is set to true, defList specify the list of the options
+ * that should be removed from original list.
+ */
+
+Datum
+optionsUpdateTexArrayWithDefList(options_spec_set * spec_set, Datum oldOptions,
+								 List *defList, bool do_reset)
+{
+	Datum		result;
+	List	   *new_values;
+	List	   *old_values;
+	List	   *merged_values;
+
+	/*
+	 * Parse and validate New values
+	 */
+	new_values = optionsDefListToRawValues(defList, do_reset);
+	if (! do_reset)
+		new_values = optionsParseRawValues(new_values, spec_set, true);
+
+	if (PointerIsValid(DatumGetPointer(oldOptions)))
+	{
+		old_values = optionsTextArrayToRawValues(oldOptions);
+		merged_values = optionsMergeOptionValues(old_values, new_values);
+	}
+	else
+	{
+		if (do_reset)
+			merged_values = NULL; /* return nothing */
+		else
+			merged_values = new_values;
+	}
+
+	/*
+	 * If we have postprocess_fun function defined in spec_set, then there
+	 * might be some custom options checks there, with error throwing. So we
+	 * should do it here to throw these errors while CREATing or ALTERing
+	 * options
+	 */
+	if (spec_set->postprocess_fun)
+	{
+		bytea	   *data = optionsValuesToBytea(merged_values, spec_set);
+
+		spec_set->postprocess_fun(data, true);
+		pfree(data);
+	}
+
+	/*
+	 * Convert options to TextArray format so caller can store them into
+	 * database
+	 */
+	result = optionsValuesToTextArray(merged_values);
+	return result;
+}
+
+/*
+ * optionsTextArrayToBytea
+ *		Convert options values from TEXT[] representation into Bytea
+ *		representation
+ *
+ * This function uses other conversion function to get desired result.
+ */
+bytea *
+optionsTextArrayToBytea(options_spec_set * spec_set, Datum data, bool validate)
+{
+	List	   *values;
+	bytea	   *options;
+
+	values = optionsTextArrayToRawValues(data);
+	values = optionsParseRawValues(values, spec_set, validate);
+	options = optionsValuesToBytea(values, spec_set);
+
+	if (spec_set->postprocess_fun)
+	{
+		spec_set->postprocess_fun(options, false);
+	}
+	return options;
+}
diff --git a/src/backend/access/common/relation.c b/src/backend/access/common/relation.c
index 1c02bf03a3..9d703cc509 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 d592655258..a79fbf0e69 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * reloptions.c
- *	  Core support for relation options (pg_class.reloptions)
+ *	  Support for relation options (pg_class.reloptions)
  *
  * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -17,13 +17,10 @@
 
 #include <float.h>
 
-#include "access/gist_private.h"
-#include "access/hash.h"
 #include "access/heaptoast.h"
 #include "access/htup_details.h"
-#include "access/nbtree.h"
 #include "access/reloptions.h"
-#include "access/spgist_private.h"
+#include "access/options.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "commands/tablespace.h"
@@ -36,6 +33,7 @@
 #include "utils/guc.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "storage/bufmgr.h"
 
 /*
  * Contents of pg_class.reloptions
@@ -93,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,225 +115,9 @@ relopt_enum_elt_def viewCheckOptValues[] =
 	{(const char *) NULL}		/* list terminator */
 };
 
-static relopt_enum enumRelOpts[] =
-{
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"check_option",
-			"View has WITH CHECK OPTION defined (local or cascaded).",
-			RELOPT_KIND_VIEW,
-			AccessExclusiveLock
-		},
-		viewCheckOptValues,
-		VIEW_OPTION_CHECK_OPTION_NOT_SET,
-		gettext_noop("Valid values are \"local\" and \"cascaded\".")
-	},
-	/* list terminator */
-	{{NULL}}
-};
-
-static relopt_string stringRelOpts[] =
-{
-	/* list terminator */
-	{{NULL}}
-};
-
-static relopt_gen **relOpts = NULL;
-static bits32 last_assigned_kind = RELOPT_KIND_LAST_DEFAULT;
-
-static int	num_custom_options = 0;
-static relopt_gen **custom_options = NULL;
-static bool need_initialization = true;
-
-static void initialize_reloptions(void);
-static void parse_one_reloption(relopt_value *option, char *text_str,
-								int text_len, bool validate);
-
-/*
- * Get the length of a string reloption (either default or the user-defined
- * value).  This is used for allocation purposes when building a set of
- * relation options.
- */
-#define GET_STRING_RELOPTION_LEN(option) \
-	((option).isset ? strlen((option).values.string_val) : \
-	 ((relopt_string *) (option).gen)->default_len)
-
-/*
- * initialize_reloptions
- *		initialization routine, must be called before parsing
- *
- * Initialize the relOpts array and fill each variable's type and name length.
- */
-static void
-initialize_reloptions(void)
-{
-	int			i;
-	int			j;
-
-	j = 0;
-	for (i = 0; boolRelOpts[i].gen.name; i++)
-	{
-		Assert(DoLockModesConflict(boolRelOpts[i].gen.lockmode,
-								   boolRelOpts[i].gen.lockmode));
-		j++;
-	}
-	for (i = 0; intRelOpts[i].gen.name; i++)
-	{
-		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
-								   intRelOpts[i].gen.lockmode));
-		j++;
-	}
-	for (i = 0; realRelOpts[i].gen.name; i++)
-	{
-		Assert(DoLockModesConflict(realRelOpts[i].gen.lockmode,
-								   realRelOpts[i].gen.lockmode));
-		j++;
-	}
-	for (i = 0; enumRelOpts[i].gen.name; i++)
-	{
-		Assert(DoLockModesConflict(enumRelOpts[i].gen.lockmode,
-								   enumRelOpts[i].gen.lockmode));
-		j++;
-	}
-	for (i = 0; stringRelOpts[i].gen.name; i++)
-	{
-		Assert(DoLockModesConflict(stringRelOpts[i].gen.lockmode,
-								   stringRelOpts[i].gen.lockmode));
-		j++;
-	}
-	j += num_custom_options;
-
-	if (relOpts)
-		pfree(relOpts);
-	relOpts = MemoryContextAlloc(TopMemoryContext,
-								 (j + 1) * sizeof(relopt_gen *));
-
-	j = 0;
-	for (i = 0; boolRelOpts[i].gen.name; i++)
-	{
-		relOpts[j] = &boolRelOpts[i].gen;
-		relOpts[j]->type = RELOPT_TYPE_BOOL;
-		relOpts[j]->namelen = strlen(relOpts[j]->name);
-		j++;
-	}
-
-	for (i = 0; intRelOpts[i].gen.name; i++)
-	{
-		relOpts[j] = &intRelOpts[i].gen;
-		relOpts[j]->type = RELOPT_TYPE_INT;
-		relOpts[j]->namelen = strlen(relOpts[j]->name);
-		j++;
-	}
-
-	for (i = 0; realRelOpts[i].gen.name; i++)
-	{
-		relOpts[j] = &realRelOpts[i].gen;
-		relOpts[j]->type = RELOPT_TYPE_REAL;
-		relOpts[j]->namelen = strlen(relOpts[j]->name);
-		j++;
-	}
-
-	for (i = 0; enumRelOpts[i].gen.name; i++)
-	{
-		relOpts[j] = &enumRelOpts[i].gen;
-		relOpts[j]->type = RELOPT_TYPE_ENUM;
-		relOpts[j]->namelen = strlen(relOpts[j]->name);
-		j++;
-	}
-
-	for (i = 0; stringRelOpts[i].gen.name; i++)
-	{
-		relOpts[j] = &stringRelOpts[i].gen;
-		relOpts[j]->type = RELOPT_TYPE_STRING;
-		relOpts[j]->namelen = strlen(relOpts[j]->name);
-		j++;
-	}
-
-	for (i = 0; i < num_custom_options; i++)
-	{
-		relOpts[j] = custom_options[i];
-		j++;
-	}
-
-	/* add a list terminator */
-	relOpts[j] = NULL;
-
-	/* flag the work is complete */
-	need_initialization = false;
-}
-
-/*
- * add_reloption_kind
- *		Create a new relopt_kind value, to be used in custom reloptions by
- *		user-defined AMs.
- */
-relopt_kind
-add_reloption_kind(void)
-{
-	/* don't hand out the last bit so that the enum's behavior is portable */
-	if (last_assigned_kind >= RELOPT_KIND_MAX)
-		ereport(ERROR,
-				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-				 errmsg("user-defined relation parameter types limit exceeded")));
-	last_assigned_kind <<= 1;
-	return (relopt_kind) last_assigned_kind;
-}
-
-/*
- * add_reloption
- *		Add an already-created custom reloption to the list, and recompute the
- *		main parser table.
- */
-static void
-add_reloption(relopt_gen *newoption)
-{
-	static int	max_custom_options = 0;
-
-	if (num_custom_options >= max_custom_options)
-	{
-		MemoryContext oldcxt;
-
-		oldcxt = MemoryContextSwitchTo(TopMemoryContext);
-
-		if (max_custom_options == 0)
-		{
-			max_custom_options = 8;
-			custom_options = palloc(max_custom_options * sizeof(relopt_gen *));
-		}
-		else
-		{
-			max_custom_options *= 2;
-			custom_options = repalloc(custom_options,
-									  max_custom_options * sizeof(relopt_gen *));
-		}
-		MemoryContextSwitchTo(oldcxt);
-	}
-	custom_options[num_custom_options++] = newoption;
 
-	need_initialization = true;
-}
+options_spec_set *get_stdrd_relopt_spec_set(bool is_for_toast);
+void oid_postvalidate(option_value* value);
 
 /*
  * init_local_reloptions
@@ -726,9 +127,8 @@ add_reloption(relopt_gen *newoption)
 void
 init_local_reloptions(local_relopts *opts, Size relopt_struct_size)
 {
-	opts->options = NIL;
 	opts->validators = NIL;
-	opts->relopt_struct_size = relopt_struct_size;
+	opts->spec_set = allocateOptionsSpecSet(NULL, relopt_struct_size, true, 0);
 }
 
 /*
@@ -742,112 +142,6 @@ register_reloptions_validator(local_relopts *opts, relopts_validator validator)
 	opts->validators = lappend(opts->validators, validator);
 }
 
-/*
- * add_local_reloption
- *		Add an already-created custom reloption to the local list.
- */
-static void
-add_local_reloption(local_relopts *relopts, relopt_gen *newoption, int offset)
-{
-	local_relopt *opt = palloc(sizeof(*opt));
-
-	Assert(offset < relopts->relopt_struct_size);
-
-	opt->option = newoption;
-	opt->offset = offset;
-
-	relopts->options = lappend(relopts->options, opt);
-}
-
-/*
- * allocate_reloption
- *		Allocate a new reloption and initialize the type-agnostic fields
- *		(for types other than string)
- */
-static relopt_gen *
-allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
-				   LOCKMODE lockmode)
-{
-	MemoryContext oldcxt;
-	size_t		size;
-	relopt_gen *newoption;
-
-	if (kinds != RELOPT_KIND_LOCAL)
-		oldcxt = MemoryContextSwitchTo(TopMemoryContext);
-	else
-		oldcxt = NULL;
-
-	switch (type)
-	{
-		case RELOPT_TYPE_BOOL:
-			size = sizeof(relopt_bool);
-			break;
-		case RELOPT_TYPE_INT:
-			size = sizeof(relopt_int);
-			break;
-		case RELOPT_TYPE_REAL:
-			size = sizeof(relopt_real);
-			break;
-		case RELOPT_TYPE_ENUM:
-			size = sizeof(relopt_enum);
-			break;
-		case RELOPT_TYPE_STRING:
-			size = sizeof(relopt_string);
-			break;
-		default:
-			elog(ERROR, "unsupported reloption type %d", type);
-			return NULL;		/* keep compiler quiet */
-	}
-
-	newoption = palloc(size);
-
-	newoption->name = pstrdup(name);
-	if (desc)
-		newoption->desc = pstrdup(desc);
-	else
-		newoption->desc = NULL;
-	newoption->kinds = kinds;
-	newoption->namelen = strlen(name);
-	newoption->type = type;
-	newoption->lockmode = lockmode;
-
-	if (oldcxt != NULL)
-		MemoryContextSwitchTo(oldcxt);
-
-	return newoption;
-}
-
-/*
- * init_bool_reloption
- *		Allocate and initialize a new boolean reloption
- */
-static relopt_bool *
-init_bool_reloption(bits32 kinds, const char *name, const char *desc,
-					bool default_val, LOCKMODE lockmode)
-{
-	relopt_bool *newoption;
-
-	newoption = (relopt_bool *) allocate_reloption(kinds, RELOPT_TYPE_BOOL,
-												   name, desc, lockmode);
-	newoption->default_val = default_val;
-
-	return newoption;
-}
-
-/*
- * add_bool_reloption
- *		Add a new boolean reloption
- */
-void
-add_bool_reloption(bits32 kinds, const char *name, const char *desc,
-				   bool default_val, LOCKMODE lockmode)
-{
-	relopt_bool *newoption = init_bool_reloption(kinds, name, desc,
-												 default_val, lockmode);
-
-	add_reloption((relopt_gen *) newoption);
-}
-
 /*
  * add_local_bool_reloption
  *		Add a new boolean local reloption
@@ -858,47 +152,8 @@ void
 add_local_bool_reloption(local_relopts *relopts, const char *name,
 						 const char *desc, bool default_val, int offset)
 {
-	relopt_bool *newoption = init_bool_reloption(RELOPT_KIND_LOCAL,
-												 name, desc,
-												 default_val, 0);
-
-	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
-}
-
-
-/*
- * init_real_reloption
- *		Allocate and initialize a new integer reloption
- */
-static relopt_int *
-init_int_reloption(bits32 kinds, const char *name, const char *desc,
-				   int default_val, int min_val, int max_val,
-				   LOCKMODE lockmode)
-{
-	relopt_int *newoption;
-
-	newoption = (relopt_int *) allocate_reloption(kinds, RELOPT_TYPE_INT,
-												  name, desc, lockmode);
-	newoption->default_val = default_val;
-	newoption->min = min_val;
-	newoption->max = max_val;
-
-	return newoption;
-}
-
-/*
- * add_int_reloption
- *		Add a new integer reloption
- */
-void
-add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val,
-				  int min_val, int max_val, LOCKMODE lockmode)
-{
-	relopt_int *newoption = init_int_reloption(kinds, name, desc,
-											   default_val, min_val,
-											   max_val, lockmode);
-
-	add_reloption((relopt_gen *) newoption);
+	optionsSpecSetAddBool(relopts->spec_set, name, desc, NoLock, offset, NULL,
+																default_val);
 }
 
 /*
@@ -909,453 +164,59 @@ add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_
  */
 void
 add_local_int_reloption(local_relopts *relopts, const char *name,
-						const char *desc, int default_val, int min_val,
-						int max_val, int offset)
-{
-	relopt_int *newoption = init_int_reloption(RELOPT_KIND_LOCAL,
-											   name, desc, default_val,
-											   min_val, max_val, 0);
-
-	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
-}
-
-/*
- * init_real_reloption
- *		Allocate and initialize a new real reloption
- */
-static relopt_real *
-init_real_reloption(bits32 kinds, const char *name, const char *desc,
-					double default_val, double min_val, double max_val,
-					LOCKMODE lockmode)
-{
-	relopt_real *newoption;
-
-	newoption = (relopt_real *) allocate_reloption(kinds, RELOPT_TYPE_REAL,
-												   name, desc, lockmode);
-	newoption->default_val = default_val;
-	newoption->min = min_val;
-	newoption->max = max_val;
-
-	return newoption;
-}
-
-/*
- * add_real_reloption
- *		Add a new float reloption
- */
-void
-add_real_reloption(bits32 kinds, const char *name, const char *desc,
-				   double default_val, double min_val, double max_val,
-				   LOCKMODE lockmode)
-{
-	relopt_real *newoption = init_real_reloption(kinds, name, desc,
-												 default_val, min_val,
-												 max_val, lockmode);
-
-	add_reloption((relopt_gen *) newoption);
-}
-
-/*
- * add_local_real_reloption
- *		Add a new local float reloption
- *
- * 'offset' is offset of double-typed field.
- */
-void
-add_local_real_reloption(local_relopts *relopts, const char *name,
-						 const char *desc, double default_val,
-						 double min_val, double max_val, int offset)
-{
-	relopt_real *newoption = init_real_reloption(RELOPT_KIND_LOCAL,
-												 name, desc,
-												 default_val, min_val,
-												 max_val, 0);
-
-	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
-}
-
-/*
- * init_enum_reloption
- *		Allocate and initialize a new enum reloption
- */
-static relopt_enum *
-init_enum_reloption(bits32 kinds, const char *name, const char *desc,
-					relopt_enum_elt_def *members, int default_val,
-					const char *detailmsg, LOCKMODE lockmode)
-{
-	relopt_enum *newoption;
-
-	newoption = (relopt_enum *) allocate_reloption(kinds, RELOPT_TYPE_ENUM,
-												   name, desc, lockmode);
-	newoption->members = members;
-	newoption->default_val = default_val;
-	newoption->detailmsg = detailmsg;
-
-	return newoption;
-}
-
-
-/*
- * add_enum_reloption
- *		Add a new enum reloption
- *
- * The members array must have a terminating NULL entry.
- *
- * The detailmsg is shown when unsupported values are passed, and has this
- * form:   "Valid values are \"foo\", \"bar\", and \"bar\"."
- *
- * The members array and detailmsg are not copied -- caller must ensure that
- * they are valid throughout the life of the process.
- */
-void
-add_enum_reloption(bits32 kinds, const char *name, const char *desc,
-				   relopt_enum_elt_def *members, int default_val,
-				   const char *detailmsg, LOCKMODE lockmode)
-{
-	relopt_enum *newoption = init_enum_reloption(kinds, name, desc,
-												 members, default_val,
-												 detailmsg, lockmode);
-
-	add_reloption((relopt_gen *) newoption);
-}
-
-/*
- * add_local_enum_reloption
- *		Add a new local enum reloption
- *
- * 'offset' is offset of int-typed field.
- */
-void
-add_local_enum_reloption(local_relopts *relopts, const char *name,
-						 const char *desc, relopt_enum_elt_def *members,
-						 int default_val, const char *detailmsg, int offset)
-{
-	relopt_enum *newoption = init_enum_reloption(RELOPT_KIND_LOCAL,
-												 name, desc,
-												 members, default_val,
-												 detailmsg, 0);
-
-	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
-}
-
-/*
- * init_string_reloption
- *		Allocate and initialize a new string reloption
- */
-static relopt_string *
-init_string_reloption(bits32 kinds, const char *name, const char *desc,
-					  const char *default_val,
-					  validate_string_relopt validator,
-					  fill_string_relopt filler,
-					  LOCKMODE lockmode)
-{
-	relopt_string *newoption;
-
-	/* make sure the validator/default combination is sane */
-	if (validator)
-		(validator) (default_val);
-
-	newoption = (relopt_string *) allocate_reloption(kinds, RELOPT_TYPE_STRING,
-													 name, desc, lockmode);
-	newoption->validate_cb = validator;
-	newoption->fill_cb = filler;
-	if (default_val)
-	{
-		if (kinds == RELOPT_KIND_LOCAL)
-			newoption->default_val = strdup(default_val);
-		else
-			newoption->default_val = MemoryContextStrdup(TopMemoryContext, default_val);
-		newoption->default_len = strlen(default_val);
-		newoption->default_isnull = false;
-	}
-	else
-	{
-		newoption->default_val = "";
-		newoption->default_len = 0;
-		newoption->default_isnull = true;
-	}
-
-	return newoption;
-}
-
-/*
- * add_string_reloption
- *		Add a new string reloption
- *
- * "validator" is an optional function pointer that can be used to test the
- * validity of the values.  It must elog(ERROR) when the argument string is
- * not acceptable for the variable.  Note that the default value must pass
- * the validation.
- */
-void
-add_string_reloption(bits32 kinds, const char *name, const char *desc,
-					 const char *default_val, validate_string_relopt validator,
-					 LOCKMODE lockmode)
-{
-	relopt_string *newoption = init_string_reloption(kinds, name, desc,
-													 default_val,
-													 validator, NULL,
-													 lockmode);
-
-	add_reloption((relopt_gen *) newoption);
-}
-
-/*
- * add_local_string_reloption
- *		Add a new local string reloption
- *
- * 'offset' is offset of int-typed field that will store offset of string value
- * in the resulting bytea structure.
- */
-void
-add_local_string_reloption(local_relopts *relopts, const char *name,
-						   const char *desc, const char *default_val,
-						   validate_string_relopt validator,
-						   fill_string_relopt filler, int offset)
-{
-	relopt_string *newoption = init_string_reloption(RELOPT_KIND_LOCAL,
-													 name, desc,
-													 default_val,
-													 validator, filler,
-													 0);
-
-	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
-}
-
-/*
- * Transform a relation options list (list of DefElem) into the text array
- * format that is kept in pg_class.reloptions, including only those options
- * that are in the passed namespace.  The output values do not include the
- * namespace.
- *
- * This is used for three cases: CREATE TABLE/INDEX, ALTER TABLE SET, and
- * ALTER TABLE RESET.  In the ALTER cases, oldOptions is the existing
- * reloptions value (possibly NULL), and we replace or remove entries
- * as needed.
- *
- * If acceptOidsOff is true, then we allow oids = false, but throw error when
- * on. This is solely needed for backwards compatibility.
- *
- * Note that this is not responsible for determining whether the options
- * are valid, but it does check that namespaces for all the options given are
- * listed in validnsps.  The NULL namespace is always valid and need not be
- * explicitly listed.  Passing a NULL pointer means that only the NULL
- * namespace is valid.
- *
- * Both oldOptions and the result are text arrays (or NULL for "default"),
- * but we declare them as Datums to avoid including array.h in reloptions.h.
- */
-Datum
-transformRelOptions(Datum oldOptions, List *defList, const char *namspace,
-					char *validnsps[], bool acceptOidsOff, bool isReset)
-{
-	Datum		result;
-	ArrayBuildState *astate;
-	ListCell   *cell;
-
-	/* no change if empty list */
-	if (defList == NIL)
-		return oldOptions;
-
-	/* We build new array using accumArrayResult */
-	astate = NULL;
-
-	/* Copy any oldOptions that aren't to be replaced */
-	if (PointerIsValid(DatumGetPointer(oldOptions)))
-	{
-		ArrayType  *array = DatumGetArrayTypeP(oldOptions);
-		Datum	   *oldoptions;
-		int			noldoptions;
-		int			i;
-
-		deconstruct_array(array, TEXTOID, -1, false, TYPALIGN_INT,
-						  &oldoptions, NULL, &noldoptions);
-
-		for (i = 0; i < noldoptions; i++)
-		{
-			char	   *text_str = VARDATA(oldoptions[i]);
-			int			text_len = VARSIZE(oldoptions[i]) - VARHDRSZ;
-
-			/* Search for a match in defList */
-			foreach(cell, defList)
-			{
-				DefElem    *def = (DefElem *) lfirst(cell);
-				int			kw_len;
-
-				/* ignore if not in the same namespace */
-				if (namspace == NULL)
-				{
-					if (def->defnamespace != NULL)
-						continue;
-				}
-				else if (def->defnamespace == NULL)
-					continue;
-				else if (strcmp(def->defnamespace, namspace) != 0)
-					continue;
-
-				kw_len = strlen(def->defname);
-				if (text_len > kw_len && text_str[kw_len] == '=' &&
-					strncmp(text_str, def->defname, kw_len) == 0)
-					break;
-			}
-			if (!cell)
-			{
-				/* No match, so keep old option */
-				astate = accumArrayResult(astate, oldoptions[i],
-										  false, TEXTOID,
-										  CurrentMemoryContext);
-			}
-		}
-	}
-
-	/*
-	 * If CREATE/SET, add new options to array; if RESET, just check that the
-	 * user didn't say RESET (option=val).  (Must do this because the grammar
-	 * doesn't enforce it.)
-	 */
-	foreach(cell, defList)
-	{
-		DefElem    *def = (DefElem *) lfirst(cell);
-
-		if (isReset)
-		{
-			if (def->arg != NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_SYNTAX_ERROR),
-						 errmsg("RESET must not include values for parameters")));
-		}
-		else
-		{
-			text	   *t;
-			const char *value;
-			Size		len;
-
-			/*
-			 * Error out if the namespace is not valid.  A NULL namespace is
-			 * always valid.
-			 */
-			if (def->defnamespace != NULL)
-			{
-				bool		valid = false;
-				int			i;
-
-				if (validnsps)
-				{
-					for (i = 0; validnsps[i]; i++)
-					{
-						if (strcmp(def->defnamespace, validnsps[i]) == 0)
-						{
-							valid = true;
-							break;
-						}
-					}
-				}
-
-				if (!valid)
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-							 errmsg("unrecognized parameter namespace \"%s\"",
-									def->defnamespace)));
-			}
-
-			/* ignore if not in the same namespace */
-			if (namspace == NULL)
-			{
-				if (def->defnamespace != NULL)
-					continue;
-			}
-			else if (def->defnamespace == NULL)
-				continue;
-			else if (strcmp(def->defnamespace, namspace) != 0)
-				continue;
-
-			/*
-			 * Flatten the DefElem into a text string like "name=arg". If we
-			 * have just "name", assume "name=true" is meant.  Note: the
-			 * namespace is not output.
-			 */
-			if (def->arg != NULL)
-				value = defGetString(def);
-			else
-				value = "true";
-
-			/*
-			 * This is not a great place for this test, but there's no other
-			 * convenient place to filter the option out. As WITH (oids =
-			 * false) will be removed someday, this seems like an acceptable
-			 * amount of ugly.
-			 */
-			if (acceptOidsOff && def->defnamespace == NULL &&
-				strcmp(def->defname, "oids") == 0)
-			{
-				if (defGetBoolean(def))
-					ereport(ERROR,
-							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-							 errmsg("tables declared WITH OIDS are not supported")));
-				/* skip over option, reloptions machinery doesn't know it */
-				continue;
-			}
-
-			len = VARHDRSZ + strlen(def->defname) + 1 + strlen(value);
-			/* +1 leaves room for sprintf's trailing null */
-			t = (text *) palloc(len + 1);
-			SET_VARSIZE(t, len);
-			sprintf(VARDATA(t), "%s=%s", def->defname, value);
-
-			astate = accumArrayResult(astate, PointerGetDatum(t),
-									  false, TEXTOID,
-									  CurrentMemoryContext);
-		}
-	}
-
-	if (astate)
-		result = makeArrayResult(astate, CurrentMemoryContext);
-	else
-		result = (Datum) 0;
-
-	return result;
+						const char *desc, int default_val, int min_val,
+						int max_val, int offset)
+{
+	optionsSpecSetAddInt(relopts->spec_set, name, desc, NoLock, offset, NULL,
+												default_val, min_val, max_val);
 }
 
-
 /*
- * Convert the text-array format of reloptions into a List of DefElem.
- * This is the inverse of transformRelOptions().
+ * add_local_real_reloption
+ *		Add a new local float reloption
+ *
+ * 'offset' is offset of double-typed field.
  */
-List *
-untransformRelOptions(Datum options)
+void
+add_local_real_reloption(local_relopts *relopts, const char *name,
+						 const char *desc, double default_val,
+						 double min_val, double max_val, int offset)
 {
-	List	   *result = NIL;
-	ArrayType  *array;
-	Datum	   *optiondatums;
-	int			noptions;
-	int			i;
+	optionsSpecSetAddReal(relopts->spec_set, name, desc, NoLock, offset, NULL,
+												default_val, min_val, max_val);
 
-	/* 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));
-	}
+/*
+ * add_local_enum_reloption
+ *		Add a new local enum reloption
+ *
+ * 'offset' is offset of int-typed field.
+ */
+void
+add_local_enum_reloption(local_relopts *relopts, const char *name,
+						 const char *desc, opt_enum_elt_def *members,
+						 int default_val, const char *detailmsg, int offset)
+{
+	optionsSpecSetAddEnum(relopts->spec_set, name, desc, NoLock, offset, NULL,
+											members, default_val, detailmsg);
+}
 
-	return result;
+/*
+ * add_local_string_reloption
+ *		Add a new local string reloption
+ *
+ * 'offset' is offset of int-typed field that will store offset of string value
+ * in the resulting bytea structure.
+ */
+void
+add_local_string_reloption(local_relopts *relopts, const char *name,
+						   const char *desc, const char *default_val,
+						   validate_string_relopt validator,
+						   fill_string_relopt filler, int offset)
+{
+	optionsSpecSetAddString(relopts->spec_set, name, desc, NoLock, offset,
+							NULL, default_val, validator, filler);
 }
 
 /*
@@ -1372,12 +233,13 @@ untransformRelOptions(Datum options)
  */
 bytea *
 extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
-				  amoptions_function amoptions)
+				  amreloptspecset_function amoptionsspecsetfn)
 {
 	bytea	   *options;
 	bool		isnull;
 	Datum		datum;
 	Form_pg_class classForm;
+	options_spec_set *spec_set;
 
 	datum = fastgetattr(tuple,
 						Anum_pg_class_reloptions,
@@ -1391,545 +253,268 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 	/* Parse into appropriate format; don't error out here */
 	switch (classForm->relkind)
 	{
-		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
+			spec_set = get_toast_relopt_spec_set();
+			break;
+		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
-			options = heap_reloptions(classForm->relkind, datum, false);
+			spec_set = get_heap_relopt_spec_set();
 			break;
 		case RELKIND_PARTITIONED_TABLE:
-			options = partitioned_table_reloptions(datum, false);
+			spec_set = get_partitioned_relopt_spec_set();
 			break;
 		case RELKIND_VIEW:
-			options = view_reloptions(datum, false);
+			spec_set = get_view_relopt_spec_set();
 			break;
 		case RELKIND_INDEX:
 		case RELKIND_PARTITIONED_INDEX:
-			options = index_reloptions(amoptions, datum, false);
+			if (amoptionsspecsetfn)
+				spec_set = amoptionsspecsetfn();
+			else
+				spec_set = NULL;
 			break;
 		case RELKIND_FOREIGN_TABLE:
-			options = NULL;
+			spec_set = NULL;
 			break;
 		default:
 			Assert(false);		/* can't get here */
-			options = NULL;		/* keep compiler quiet */
+			spec_set = NULL;		/* keep compiler quiet */
 			break;
 	}
-
+	if (spec_set)
+		options = optionsTextArrayToBytea(spec_set, datum, 0);
+	else
+		options = NULL;
 	return options;
 }
 
-static void
-parseRelOptionsInternal(Datum options, bool validate,
-						relopt_value *reloptions, int numoptions)
-{
-	ArrayType  *array = DatumGetArrayTypeP(options);
-	Datum	   *optiondatums;
-	int			noptions;
-	int			i;
-
-	deconstruct_array(array, TEXTOID, -1, false, TYPALIGN_INT,
-					  &optiondatums, NULL, &noptions);
-
-	for (i = 0; i < noptions; i++)
-	{
-		char	   *text_str = VARDATA(optiondatums[i]);
-		int			text_len = VARSIZE(optiondatums[i]) - VARHDRSZ;
-		int			j;
-
-		/* Search for a match in reloptions */
-		for (j = 0; j < numoptions; j++)
-		{
-			int			kw_len = reloptions[j].gen->namelen;
-
-			if (text_len > kw_len && text_str[kw_len] == '=' &&
-				strncmp(text_str, reloptions[j].gen->name, kw_len) == 0)
-			{
-				parse_one_reloption(&reloptions[j], text_str, text_len,
-									validate);
-				break;
-			}
-		}
-
-		if (j >= numoptions && validate)
-		{
-			char	   *s;
-			char	   *p;
-
-			s = TextDatumGetCString(optiondatums[i]);
-			p = strchr(s, '=');
-			if (p)
-				*p = '\0';
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-					 errmsg("unrecognized parameter \"%s\"", s)));
-		}
-	}
-
-	/* It's worth avoiding memory leaks in this function */
-	pfree(optiondatums);
-
-	if (((void *) array) != DatumGetPointer(options))
-		pfree(array);
-}
-
-/*
- * Interpret reloptions that are given in text-array format.
- *
- * options is a reloption text array as constructed by transformRelOptions.
- * kind specifies the family of options to be processed.
- *
- * The return value is a relopt_value * array on which the options actually
- * set in the options array are marked with isset=true.  The length of this
- * array is returned in *numrelopts.  Options not set are also present in the
- * array; this is so that the caller can easily locate the default values.
- *
- * If there are no options of the given kind, numrelopts is set to 0 and NULL
- * is returned (unless options are illegally supplied despite none being
- * defined, in which case an error occurs).
- *
- * Note: values of type int, bool and real are allocated as part of the
- * returned array.  Values of type string are allocated separately and must
- * be freed by the caller.
- */
-static relopt_value *
-parseRelOptions(Datum options, bool validate, relopt_kind kind,
-				int *numrelopts)
-{
-	relopt_value *reloptions = NULL;
-	int			numoptions = 0;
-	int			i;
-	int			j;
-
-	if (need_initialization)
-		initialize_reloptions();
-
-	/* Build a list of expected options, based on kind */
-
-	for (i = 0; relOpts[i]; i++)
-		if (relOpts[i]->kinds & kind)
-			numoptions++;
-
-	if (numoptions > 0)
-	{
-		reloptions = palloc(numoptions * sizeof(relopt_value));
-
-		for (i = 0, j = 0; relOpts[i]; i++)
-		{
-			if (relOpts[i]->kinds & kind)
-			{
-				reloptions[j].gen = relOpts[i];
-				reloptions[j].isset = false;
-				j++;
-			}
-		}
-	}
-
-	/* Done if no options */
-	if (PointerIsValid(DatumGetPointer(options)))
-		parseRelOptionsInternal(options, validate, reloptions, numoptions);
-
-	*numrelopts = numoptions;
-	return reloptions;
-}
-
-/* Parse local unregistered options. */
-static relopt_value *
-parseLocalRelOptions(local_relopts *relopts, Datum options, bool validate)
+void
+oid_postvalidate(option_value* value)
 {
-	int			nopts = list_length(relopts->options);
-	relopt_value *values = palloc(sizeof(*values) * nopts);
-	ListCell   *lc;
-	int			i = 0;
-
-	foreach(lc, relopts->options)
-	{
-		local_relopt *opt = lfirst(lc);
-
-		values[i].gen = opt->option;
-		values[i].isset = false;
-
-		i++;
-	}
-
-	if (options != (Datum) 0)
-		parseRelOptionsInternal(options, validate, values, nopts);
-
-	return values;
+	if (value->values.bool_val)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("tables declared WITH OIDS are not supported")));
 }
 
 /*
- * Subroutine for parseRelOptions, to parse and validate a single option's
- * value
+ * These toast options are can't be set via SQL, but we should set them
+ * to their defaults in binary representation, to make postgres work properly
  */
 static void
-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;
+toast_options_postprocess(void *data, bool validate)
+{
+	StdRdOptions * toast_options = (StdRdOptions *) data;
+	toast_options->fillfactor = 100;
+	toast_options->autovacuum.analyze_threshold = -1;
+	toast_options->autovacuum.analyze_scale_factor = -1;
+}
+
+
+options_spec_set *
+get_stdrd_relopt_spec_set(bool is_heap)
+{
+	options_spec_set * stdrd_relopt_spec_set = allocateOptionsSpecSet(
+					is_heap ? NULL : "toast",  sizeof(StdRdOptions), false, 0);
+	if (!is_heap)
+		stdrd_relopt_spec_set->postprocess_fun = toast_options_postprocess;
+
+	if (is_heap)
+		optionsSpecSetAddInt(stdrd_relopt_spec_set, "fillfactor",
+							"Packs table pages only to this percentag",
+							ShareUpdateExclusiveLock, /* since it applies only
+													   * to later inserts */
+							offsetof(StdRdOptions, fillfactor),  NULL,
+							HEAP_DEFAULT_FILLFACTOR, HEAP_MIN_FILLFACTOR, 100);
+
+	optionsSpecSetAddBool(stdrd_relopt_spec_set, "autovacuum_enabled",
+							"Enables autovacuum in this relation",
+							ShareUpdateExclusiveLock,
+							offsetof(StdRdOptions, autovacuum) +
+												offsetof(AutoVacOpts, enabled),
+							NULL, true);
+
+	optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_vacuum_threshold",
+							"Minimum number of tuple updates or deletes prior to vacuum",
+							ShareUpdateExclusiveLock,
+							offsetof(StdRdOptions, autovacuum) +
+									   offsetof(AutoVacOpts, vacuum_threshold),
+							NULL, -1, 0, INT_MAX);
+
+	if (is_heap)
+		optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_analyze_threshold",
+							 "Minimum number of tuple updates or deletes prior to vacuum",
+							 ShareUpdateExclusiveLock,
+							 offsetof(StdRdOptions, autovacuum) +
+									  offsetof(AutoVacOpts, analyze_threshold),
+							 NULL, -1, 0, INT_MAX);
+
+	optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_vacuum_cost_limit",
+						 "Vacuum cost amount available before napping, for autovacuum",
+						 ShareUpdateExclusiveLock,
+						 offsetof(StdRdOptions, autovacuum) +
+									  offsetof(AutoVacOpts, vacuum_cost_limit),
+						 NULL, -1, 0, 10000);
+
+	optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_freeze_min_age",
+						 "Minimum age at which VACUUM should freeze a table row, for autovacuum",
+						 ShareUpdateExclusiveLock,
+						 offsetof(StdRdOptions, autovacuum) +
+										 offsetof(AutoVacOpts, freeze_min_age),
+						 NULL, -1, 0, 1000000000);
+
+	optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_freeze_max_age",
+						 "Age at which to autovacuum a table to prevent transaction ID wraparound",
+						 ShareUpdateExclusiveLock,
+						 offsetof(StdRdOptions, autovacuum) +
+										 offsetof(AutoVacOpts, freeze_max_age),
+						 NULL, -1, 100000, 2000000000);
+
+	optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_freeze_table_age",
+						 "Age at which VACUUM should perform a full table sweep to freeze row versions",
+						 ShareUpdateExclusiveLock,
+						 offsetof(StdRdOptions, autovacuum) +
+									   offsetof(AutoVacOpts, freeze_table_age),
+						 NULL, -1, 0, 2000000000);
+
+	optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_multixact_freeze_min_age",
+						 "Minimum multixact age at which VACUUM should freeze a row multixact's, for autovacuum",
+						 ShareUpdateExclusiveLock,
+						 offsetof(StdRdOptions, autovacuum) +
+							   offsetof(AutoVacOpts, multixact_freeze_min_age),
+						 NULL, -1, 0, 1000000000);
+
+	optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_multixact_freeze_max_age",
+						 "Multixact age at which to autovacuum a table to prevent multixact wraparound",
+						 ShareUpdateExclusiveLock,
+						 offsetof(StdRdOptions, autovacuum) +
+							   offsetof(AutoVacOpts, multixact_freeze_max_age),
+						 NULL, -1, 10000, 2000000000);
+
+	optionsSpecSetAddInt(stdrd_relopt_spec_set, "autovacuum_multixact_freeze_table_age",
+						 "Age of multixact at which VACUUM should perform a full table sweep to freeze row versions",
+						 ShareUpdateExclusiveLock,
+						 offsetof(StdRdOptions, autovacuum) +
+							 offsetof(AutoVacOpts, multixact_freeze_table_age),
+						 NULL, -1, 0, 2000000000);
+
+	optionsSpecSetAddInt(stdrd_relopt_spec_set,"log_autovacuum_min_duration",
+						 "Sets the minimum execution time above which autovacuum actions will be logged",
+						 ShareUpdateExclusiveLock,
+						 offsetof(StdRdOptions, autovacuum) +
+									   offsetof(AutoVacOpts, log_min_duration),
+						 NULL, -1, -1, INT_MAX);
+
+	optionsSpecSetAddReal(stdrd_relopt_spec_set, "autovacuum_vacuum_cost_delay",
+						  "Vacuum cost delay in milliseconds, for autovacuum",
+						  ShareUpdateExclusiveLock,
+						  offsetof(StdRdOptions, autovacuum) +
+									  offsetof(AutoVacOpts, vacuum_cost_delay),
+						  NULL, -1, 0.0, 100.0);
+
+	optionsSpecSetAddReal(stdrd_relopt_spec_set, "autovacuum_vacuum_scale_factor",
+						  "Number of tuple updates or deletes prior to vacuum as a fraction of reltuples",
+						  ShareUpdateExclusiveLock,
+						  offsetof(StdRdOptions, autovacuum) +
+									offsetof(AutoVacOpts, vacuum_scale_factor),
+						  NULL,  -1, 0.0, 100.0);
+
+	optionsSpecSetAddReal(stdrd_relopt_spec_set, "autovacuum_vacuum_insert_scale_factor",
+						  "Number of tuple inserts prior to vacuum as a fraction of reltuples",
+						  ShareUpdateExclusiveLock,
+						  offsetof(StdRdOptions, autovacuum) +
+								offsetof(AutoVacOpts, vacuum_ins_scale_factor),
+						  NULL,  -1, 0.0, 100.0);
+	if (is_heap)
+	{
+		optionsSpecSetAddReal(stdrd_relopt_spec_set, "autovacuum_analyze_scale_factor",
+							  "Number of tuple inserts, updates or deletes prior to analyze as a fraction of reltuples",
+							  ShareUpdateExclusiveLock,
+							  offsetof(StdRdOptions, autovacuum) +
+								   offsetof(AutoVacOpts, analyze_scale_factor),
+							  NULL,  -1, 0.0, 100.0);
+
+		optionsSpecSetAddInt(stdrd_relopt_spec_set, "toast_tuple_target",
+							 "Sets the target tuple length at which external columns will be toasted",
+							 ShareUpdateExclusiveLock,
+							 offsetof(StdRdOptions, toast_tuple_target),
+							 NULL, TOAST_TUPLE_TARGET, 128,
+							 TOAST_TUPLE_TARGET_MAIN);
+
+		optionsSpecSetAddBool(stdrd_relopt_spec_set, "user_catalog_table",
+							  "Declare a table as an additional catalog table, e.g. for the purpose of logical replication",
+							  AccessExclusiveLock,
+							  offsetof(StdRdOptions, user_catalog_table),
+							  NULL, false);
+
+		optionsSpecSetAddInt(stdrd_relopt_spec_set, "parallel_workers",
+							 "Number of parallel processes that can be used per executor node for this relation.",
+							 ShareUpdateExclusiveLock,
+							 offsetof(StdRdOptions, parallel_workers),
+							 NULL, -1, 0, 1024);
 	}
 
-	if (parsed)
-		option->isset = true;
-	if (!nofree)
-		pfree(value);
-}
-
-/*
- * Given the result from parseRelOptions, allocate a struct that's of the
- * specified base size plus any extra space that's needed for string variables.
- *
- * "base" should be sizeof(struct) of the reloptions struct (StdRdOptions or
- * equivalent).
- */
-static void *
-allocateReloptStruct(Size base, relopt_value *options, int numoptions)
-{
-	Size		size = base;
-	int			i;
-
-	for (i = 0; i < numoptions; i++)
-	{
-		relopt_value *optval = &options[i];
-
-		if (optval->gen->type == RELOPT_TYPE_STRING)
-		{
-			relopt_string *optstr = (relopt_string *) optval->gen;
-
-			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;
-		}
+	optionsSpecSetAddEnum(stdrd_relopt_spec_set, "vacuum_index_cleanup",
+						  "Controls index vacuuming and index cleanup",
+						  ShareUpdateExclusiveLock,
+						  offsetof(StdRdOptions, vacuum_index_cleanup),
+						  NULL, StdRdOptIndexCleanupValues,
+						  STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
+						  gettext_noop("Valid values are \"on\", \"off\", and \"auto\"."));
+
+	optionsSpecSetAddBool(stdrd_relopt_spec_set, "vacuum_truncate",
+						  "Enables vacuum to truncate empty pages at the end of this table",
+						  ShareUpdateExclusiveLock,
+						  offsetof(StdRdOptions, vacuum_truncate),
+						  NULL, true);
+
+	if (is_heap)
+	{
+		optionsSpecSetAddBool(stdrd_relopt_spec_set, "oids",
+							  "Backward compatibility option. Will do nothing when false, will throw error when true",
+							  NoLock,
+							  -1, /* Do not actually store it's value */
+							  &oid_postvalidate, false);
 	}
 
-	return palloc0(size);
+	return stdrd_relopt_spec_set;
 }
 
-/*
- * Given the result of parseRelOptions and a parsing table, fill in the
- * struct (previously allocated with allocateReloptStruct) with the parsed
- * values.
- *
- * rdopts is the pointer to the allocated struct to be filled.
- * basesize is the sizeof(struct) that was passed to allocateReloptStruct.
- * options, of length numoptions, is parseRelOptions' output.
- * elems, of length numelems, is the table describing the allowed options.
- * When validate is true, it is expected that all options appear in elems.
- */
-static void
-fillRelOptions(void *rdopts, Size basesize,
-			   relopt_value *options, int numoptions,
-			   bool validate,
-			   const relopt_parse_elt *elems, int numelems)
-{
-	int			i;
-	int			offset = basesize;
 
-	for (i = 0; i < numoptions; i++)
-	{
-		int			j;
-		bool		found = false;
+static options_spec_set *heap_relopt_spec_set = NULL;
 
-		for (j = 0; j < numelems; j++)
-		{
-			if (strcmp(options[i].gen->name, elems[j].optname) == 0)
-			{
-				relopt_string *optstring;
-				char	   *itempos = ((char *) rdopts) + elems[j].offset;
-				char	   *string_val;
-
-				switch (options[i].gen->type)
-				{
-					case RELOPT_TYPE_BOOL:
-						*(bool *) itempos = options[i].isset ?
-							options[i].values.bool_val :
-							((relopt_bool *) options[i].gen)->default_val;
-						break;
-					case RELOPT_TYPE_INT:
-						*(int *) itempos = options[i].isset ?
-							options[i].values.int_val :
-							((relopt_int *) options[i].gen)->default_val;
-						break;
-					case RELOPT_TYPE_REAL:
-						*(double *) itempos = options[i].isset ?
-							options[i].values.real_val :
-							((relopt_real *) options[i].gen)->default_val;
-						break;
-					case RELOPT_TYPE_ENUM:
-						*(int *) itempos = options[i].isset ?
-							options[i].values.enum_val :
-							((relopt_enum *) options[i].gen)->default_val;
-						break;
-					case RELOPT_TYPE_STRING:
-						optstring = (relopt_string *) options[i].gen;
-						if (options[i].isset)
-							string_val = options[i].values.string_val;
-						else if (!optstring->default_isnull)
-							string_val = optstring->default_val;
-						else
-							string_val = NULL;
-
-						if (optstring->fill_cb)
-						{
-							Size		size =
-							optstring->fill_cb(string_val,
-											   (char *) rdopts + offset);
-
-							if (size)
-							{
-								*(int *) itempos = offset;
-								offset += size;
-							}
-							else
-								*(int *) itempos = 0;
-						}
-						else if (string_val == NULL)
-							*(int *) itempos = 0;
-						else
-						{
-							strcpy((char *) rdopts + offset, string_val);
-							*(int *) itempos = offset;
-							offset += strlen(string_val) + 1;
-						}
-						break;
-					default:
-						elog(ERROR, "unsupported reloption type %d",
-							 options[i].gen->type);
-						break;
-				}
-				found = true;
-				break;
-			}
-		}
-		if (validate && !found)
-			elog(ERROR, "reloption \"%s\" not found in parse table",
-				 options[i].gen->name);
-	}
-	SET_VARSIZE(rdopts, offset);
+options_spec_set *
+get_heap_relopt_spec_set(void)
+{
+	if (heap_relopt_spec_set)
+		return heap_relopt_spec_set;
+	heap_relopt_spec_set = get_stdrd_relopt_spec_set(true);
+	return heap_relopt_spec_set;
 }
 
+static options_spec_set *toast_relopt_spec_set = NULL;
 
-/*
- * Option parser for anything that uses StdRdOptions.
- */
-bytea *
-default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
+options_spec_set *
+get_toast_relopt_spec_set(void)
 {
-	static const relopt_parse_elt tab[] = {
-		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
-		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
-		{"autovacuum_vacuum_threshold", RELOPT_TYPE_INT,
-		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_threshold)},
-		{"autovacuum_vacuum_insert_threshold", RELOPT_TYPE_INT,
-		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_ins_threshold)},
-		{"autovacuum_analyze_threshold", RELOPT_TYPE_INT,
-		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_threshold)},
-		{"autovacuum_vacuum_cost_limit", RELOPT_TYPE_INT,
-		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_limit)},
-		{"autovacuum_freeze_min_age", RELOPT_TYPE_INT,
-		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_min_age)},
-		{"autovacuum_freeze_max_age", RELOPT_TYPE_INT,
-		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_max_age)},
-		{"autovacuum_freeze_table_age", RELOPT_TYPE_INT,
-		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_table_age)},
-		{"autovacuum_multixact_freeze_min_age", RELOPT_TYPE_INT,
-		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_min_age)},
-		{"autovacuum_multixact_freeze_max_age", RELOPT_TYPE_INT,
-		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_max_age)},
-		{"autovacuum_multixact_freeze_table_age", RELOPT_TYPE_INT,
-		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_table_age)},
-		{"log_autovacuum_min_duration", RELOPT_TYPE_INT,
-		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, log_min_duration)},
-		{"toast_tuple_target", RELOPT_TYPE_INT,
-		offsetof(StdRdOptions, toast_tuple_target)},
-		{"autovacuum_vacuum_cost_delay", RELOPT_TYPE_REAL,
-		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_delay)},
-		{"autovacuum_vacuum_scale_factor", RELOPT_TYPE_REAL,
-		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_scale_factor)},
-		{"autovacuum_vacuum_insert_scale_factor", RELOPT_TYPE_REAL,
-		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_ins_scale_factor)},
-		{"autovacuum_analyze_scale_factor", RELOPT_TYPE_REAL,
-		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_scale_factor)},
-		{"user_catalog_table", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, user_catalog_table)},
-		{"parallel_workers", RELOPT_TYPE_INT,
-		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
-		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate)}
-	};
-
-	return (bytea *) build_reloptions(reloptions, validate, kind,
-									  sizeof(StdRdOptions),
-									  tab, lengthof(tab));
+	if (toast_relopt_spec_set)
+		return toast_relopt_spec_set;
+	toast_relopt_spec_set = get_stdrd_relopt_spec_set(false);
+	return toast_relopt_spec_set;
 }
 
-/*
- * build_reloptions
- *
- * Parses "reloptions" provided by the caller, returning them in a
- * structure containing the parsed options.  The parsing is done with
- * the help of a parsing table describing the allowed options, defined
- * by "relopt_elems" of length "num_relopt_elems".
- *
- * "validate" must be true if reloptions value is freshly built by
- * transformRelOptions(), as opposed to being read from the catalog, in which
- * case the values contained in it must already be valid.
- *
- * NULL is returned if the passed-in options did not match any of the options
- * in the parsing table, unless validate is true in which case an error would
- * be reported.
- */
-void *
-build_reloptions(Datum reloptions, bool validate,
-				 relopt_kind kind,
-				 Size relopt_struct_size,
-				 const relopt_parse_elt *relopt_elems,
-				 int num_relopt_elems)
-{
-	int			numoptions;
-	relopt_value *options;
-	void	   *rdopts;
-
-	/* parse options specific to given relation option kind */
-	options = parseRelOptions(reloptions, validate, kind, &numoptions);
-	Assert(numoptions <= num_relopt_elems);
+static options_spec_set *partitioned_relopt_spec_set = NULL;
 
-	/* if none set, we're done */
-	if (numoptions == 0)
-	{
-		Assert(options == NULL);
-		return NULL;
-	}
-
-	/* allocate and fill the structure */
-	rdopts = allocateReloptStruct(relopt_struct_size, options, numoptions);
-	fillRelOptions(rdopts, relopt_struct_size, options, numoptions,
-				   validate, relopt_elems, num_relopt_elems);
-
-	pfree(options);
+options_spec_set *
+get_partitioned_relopt_spec_set(void)
+{
+	if (partitioned_relopt_spec_set)
+		return partitioned_relopt_spec_set;
+	partitioned_relopt_spec_set = allocateOptionsSpecSet(
+					NULL,  sizeof(StdRdOptions), false, 0);
+	/* No options for now, so spec set is empty */
 
-	return rdopts;
+	return partitioned_relopt_spec_set;
 }
 
 /*
@@ -1940,157 +525,201 @@ build_reloptions(Datum reloptions, bool validate,
 void *
 build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 {
-	int			noptions = list_length(relopts->options);
-	relopt_parse_elt *elems = palloc(sizeof(*elems) * noptions);
-	relopt_value *vals;
 	void	   *opts;
-	int			i = 0;
 	ListCell   *lc;
+	List	   *values;
 
-	foreach(lc, relopts->options)
-	{
-		local_relopt *opt = lfirst(lc);
+	values = optionsTextArrayToRawValues(options);
+	values = optionsParseRawValues(values, relopts->spec_set, validate);
+	opts = optionsValuesToBytea(values, relopts->spec_set);
 
-		elems[i].optname = opt->option->name;
-		elems[i].opttype = opt->option->type;
-		elems[i].offset = opt->offset;
+	/*
+	 * Kind of ugly conversion here for backward compatibility.
+	 * Would be removed while moving opclass options to options.c API
+	 */
 
-		i++;
-	}
+	if (validate && relopts->validators)
+	{
+		int 		  val_count = 0;
+		int			  i;
+		option_value *val_array;
 
-	vals = parseLocalRelOptions(relopts, options, validate);
-	opts = allocateReloptStruct(relopts->relopt_struct_size, vals, noptions);
-	fillRelOptions(opts, relopts->relopt_struct_size, vals, noptions, validate,
-				   elems, noptions);
+		foreach(lc, values)
+			val_count++;
+		val_array = palloc(sizeof(option_value)*val_count);
 
-	foreach(lc, relopts->validators)
-		((relopts_validator) lfirst(lc)) (opts, vals, noptions);
+		i=0;
+		foreach(lc, values)
+		{
+			option_value *val = lfirst(lc);
+			memcpy(&(val_array[i]), val, sizeof(option_value));
+			i++;
+		}
 
-	if (elems)
-		pfree(elems);
+		foreach(lc, relopts->validators)
+			((relopts_validator) lfirst(lc)) (opts, val_array, val_count);
 
+		pfree(val_array);
+	}
 	return opts;
-}
 
-/*
- * Option parser for partitioned tables
- */
-bytea *
-partitioned_table_reloptions(Datum reloptions, bool validate)
-{
-	/*
-	 * There are no options for partitioned tables yet, but this is able to do
-	 * some validation.
-	 */
-	return (bytea *) build_reloptions(reloptions, validate,
-									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
 }
 
 /*
- * Option parser for views
+ * get_view_relopt_spec_set
+ *		Returns an options catalog for view relation.
  */
-bytea *
-view_reloptions(Datum reloptions, bool validate)
+static options_spec_set *view_relopt_spec_set = NULL;
+
+options_spec_set *
+get_view_relopt_spec_set(void)
 {
-	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));
+	if (view_relopt_spec_set)
+		return view_relopt_spec_set;
+
+	view_relopt_spec_set = allocateOptionsSpecSet(NULL,
+											 sizeof(ViewOptions), false, 2);
+
+	optionsSpecSetAddBool(view_relopt_spec_set, "security_barrier",
+							  "View acts as a row security barrier",
+							  AccessExclusiveLock,
+					  offsetof(ViewOptions, security_barrier), NULL, false);
+
+	optionsSpecSetAddEnum(view_relopt_spec_set, "check_option",
+						   "View has WITH CHECK OPTION defined (local or cascaded)",
+							  AccessExclusiveLock,
+							  offsetof(ViewOptions, check_option), NULL,
+							  viewCheckOptValues,
+							  VIEW_OPTION_CHECK_OPTION_NOT_SET,
+							  gettext_noop("Valid values are \"local\" and \"cascaded\"."));
+
+	return view_relopt_spec_set;
 }
 
 /*
- * Parse options for heaps, views and toast tables.
+ * get_attribute_options_spec_set
+ *		Returns an options spec set for heap attributes
  */
-bytea *
-heap_reloptions(char relkind, Datum reloptions, bool validate)
+static options_spec_set *attribute_options_spec_set = NULL;
+
+options_spec_set *
+get_attribute_options_spec_set(void)
 {
-	StdRdOptions *rdopts;
+	if (attribute_options_spec_set)
+			return attribute_options_spec_set;
 
-	switch (relkind)
-	{
-		case RELKIND_TOASTVALUE:
-			rdopts = (StdRdOptions *)
-				default_reloptions(reloptions, validate, RELOPT_KIND_TOAST);
-			if (rdopts != NULL)
-			{
-				/* adjust default-only parameters for TOAST relations */
-				rdopts->fillfactor = 100;
-				rdopts->autovacuum.analyze_threshold = -1;
-				rdopts->autovacuum.analyze_scale_factor = -1;
-			}
-			return (bytea *) rdopts;
-		case RELKIND_RELATION:
-		case RELKIND_MATVIEW:
-			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
-		default:
-			/* other relkinds are not supported */
-			return NULL;
-	}
+	attribute_options_spec_set = allocateOptionsSpecSet(NULL,
+										   sizeof(AttributeOpts), false, 2);
+
+	optionsSpecSetAddReal(attribute_options_spec_set, "n_distinct",
+						  "Sets the planner's estimate of the number of distinct values appearing in a column (excluding child relations).",
+						  ShareUpdateExclusiveLock,
+			   offsetof(AttributeOpts, n_distinct), NULL, 0, -1.0, DBL_MAX);
+
+	optionsSpecSetAddReal(attribute_options_spec_set,
+						  "n_distinct_inherited",
+						  "Sets the planner's estimate of the number of distinct values appearing in a column (including child relations).",
+						  ShareUpdateExclusiveLock,
+	 offsetof(AttributeOpts, n_distinct_inherited), NULL, 0, -1.0, DBL_MAX);
+
+	return attribute_options_spec_set;
 }
 
 
 /*
- * Parse options for indexes.
- *
- *	amoptions	index AM's option parser function
- *	reloptions	options as text[] datum
- *	validate	error flag
- */
-bytea *
-index_reloptions(amoptions_function amoptions, Datum reloptions, bool validate)
+ * get_tablespace_options_spec_set
+ *		Returns an options spec set for tablespaces
+*/
+static options_spec_set *tablespace_options_spec_set = NULL;
+
+options_spec_set *
+get_tablespace_options_spec_set(void)
 {
-	Assert(amoptions != NULL);
+	if (!tablespace_options_spec_set)
+	{
+		tablespace_options_spec_set = allocateOptionsSpecSet(NULL,
+											  sizeof(TableSpaceOpts), false, 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,
+							  offsetof(TableSpaceOpts, random_page_cost),
+							  NULL, -1, 0.0, DBL_MAX);
 
-	return amoptions(reloptions, validate);
-}
+		optionsSpecSetAddReal(tablespace_options_spec_set, "seq_page_cost",
+							  "Sets the planner's estimate of the cost of a sequentially fetched disk page",
+							  ShareUpdateExclusiveLock,
+							  offsetof(TableSpaceOpts, seq_page_cost),
+							  NULL, -1, 0.0, DBL_MAX);
 
-/*
- * Option parser for attribute reloptions
- */
-bytea *
-attribute_reloptions(Datum reloptions, bool validate)
-{
-	static const relopt_parse_elt tab[] = {
-		{"n_distinct", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct)},
-		{"n_distinct_inherited", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct_inherited)}
-	};
-
-	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,
+							offsetof(TableSpaceOpts, effective_io_concurrency),
+							NULL,
+#ifdef USE_PREFETCH
+							-1, 0, MAX_IO_CONCURRENCY
+#else
+							0, 0, 0
+#endif
+			);
+
+		optionsSpecSetAddInt(tablespace_options_spec_set,
+						  "maintenance_io_concurrency",
+						  "Number of simultaneous requests that can be handled efficiently by the disk subsystem for maintenance work.",
+						  ShareUpdateExclusiveLock,
+						  offsetof(TableSpaceOpts, maintenance_io_concurrency),
+						  NULL,
+#ifdef USE_PREFETCH
+						 -1, 0, MAX_IO_CONCURRENCY
+#else
+						 0, 0, 0
+#endif
+			);
+	}
+	return tablespace_options_spec_set;
 }
 
 /*
- * Option parser for tablespace reloptions
+ * Relation options and Lock levels:
+ *
+ * The default choice for any new option should be AccessExclusiveLock.
+ * In some cases the lock level can be reduced from there, but the lock
+ * level chosen should always conflict with itself to ensure that multiple
+ * changes aren't lost when we attempt concurrent changes.
+ * The choice of lock level depends completely upon how that parameter
+ * is used within the server, not upon how and when you'd like to change it.
+ * Safety first. Existing choices are documented here, and elsewhere in
+ * backend code where the parameters are used.
+ *
+ * In general, anything that affects the results obtained from a SELECT must be
+ * protected by AccessExclusiveLock.
+ *
+ * Autovacuum related parameters can be set at ShareUpdateExclusiveLock
+ * since they are only used by the AV procs and don't change anything
+ * currently executing.
+ *
+ * Fillfactor can be set because it applies only to subsequent changes made to
+ * data blocks, as documented in heapio.c
+ *
+ * n_distinct options can be set at ShareUpdateExclusiveLock because they
+ * are only used during ANALYZE, which uses a ShareUpdateExclusiveLock,
+ * so the ANALYZE will not be affected by in-flight changes. Changing those
+ * values has no affect until the next ANALYZE, so no need for stronger lock.
+ *
+ * Planner-related parameters can be set with ShareUpdateExclusiveLock because
+ * they only affect planning and not the correctness of the execution. Plans
+ * cannot be changed in mid-flight, so changes here could not easily result in
+ * new improved plans in any case. So we allow existing queries to continue
+ * and existing plans to survive, a small price to pay for allowing better
+ * plans to be introduced concurrently without interfering with users.
+ *
+ * Setting parallel_workers is safe, since it acts the same as
+ * max_parallel_workers_per_gather which is a USERSET parameter that doesn't
+ * affect existing plans or queries.
  */
-bytea *
-tablespace_reloptions(Datum reloptions, bool validate)
-{
-	static const relopt_parse_elt tab[] = {
-		{"random_page_cost", RELOPT_TYPE_REAL, offsetof(TableSpaceOpts, random_page_cost)},
-		{"seq_page_cost", RELOPT_TYPE_REAL, offsetof(TableSpaceOpts, seq_page_cost)},
-		{"effective_io_concurrency", RELOPT_TYPE_INT, offsetof(TableSpaceOpts, effective_io_concurrency)},
-		{"maintenance_io_concurrency", RELOPT_TYPE_INT, offsetof(TableSpaceOpts, maintenance_io_concurrency)}
-	};
-
-	return (bytea *) build_reloptions(reloptions, validate,
-									  RELOPT_KIND_TABLESPACE,
-									  sizeof(TableSpaceOpts),
-									  tab, lengthof(tab));
-}
+
 
 /*
  * Determine the required LOCKMODE from an option list.
@@ -2099,33 +728,55 @@ tablespace_reloptions(Datum reloptions, bool validate)
  * for a longer explanation of how this works.
  */
 LOCKMODE
-AlterTableGetRelOptionsLockLevel(List *defList)
+AlterTableGetRelOptionsLockLevel(Relation rel, List *defList)
 {
 	LOCKMODE	lockmode = NoLock;
 	ListCell   *cell;
+	options_spec_set *spec_set = NULL;
 
 	if (defList == NIL)
 		return AccessExclusiveLock;
 
-	if (need_initialization)
-		initialize_reloptions();
+	switch (rel->rd_rel->relkind)
+	{
+		case RELKIND_TOASTVALUE:
+			spec_set = get_toast_relopt_spec_set();
+			break;
+		case RELKIND_RELATION:
+		case RELKIND_MATVIEW:
+			spec_set = get_heap_relopt_spec_set();
+			break;
+		case RELKIND_INDEX:
+			spec_set = rel->rd_indam->amreloptspecset();
+			break;
+		case RELKIND_VIEW:
+			spec_set = get_view_relopt_spec_set();
+			break;
+		case RELKIND_PARTITIONED_TABLE:
+			spec_set = get_partitioned_relopt_spec_set();
+			break;
+		default:
+			Assert(false);		/* can't get here */
+			break;
+	}
+	Assert(spec_set);			/* No spec set - no reloption change. Should
+								 * never get here */
 
 	foreach(cell, defList)
 	{
 		DefElem    *def = (DefElem *) lfirst(cell);
+
 		int			i;
 
-		for (i = 0; relOpts[i]; i++)
+		for (i = 0; i < spec_set->num; i++)
 		{
-			if (strncmp(relOpts[i]->name,
-						def->defname,
-						relOpts[i]->namelen + 1) == 0)
-			{
-				if (lockmode < relOpts[i]->lockmode)
-					lockmode = relOpts[i]->lockmode;
-			}
+			option_spec_basic *gen = spec_set->definitions[i];
+
+			if (pg_strcasecmp(gen->name,
+							  def->defname) == 0)
+				if (lockmode < gen->lockmode)
+					lockmode = gen->lockmode;
 		}
 	}
-
 	return lockmode;
 }
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index 3d15701a01..a6117665d1 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -16,7 +16,7 @@
 
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
-#include "access/reloptions.h"
+#include "access/options.h"
 #include "access/xloginsert.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
@@ -28,6 +28,7 @@
 #include "utils/builtins.h"
 #include "utils/index_selfuncs.h"
 #include "utils/typcache.h"
+#include "utils/guc.h"
 
 
 /*
@@ -68,7 +69,6 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amvacuumcleanup = ginvacuumcleanup;
 	amroutine->amcanreturn = NULL;
 	amroutine->amcostestimate = gincostestimate;
-	amroutine->amoptions = ginoptions;
 	amroutine->amproperty = NULL;
 	amroutine->ambuildphasename = NULL;
 	amroutine->amvalidate = ginvalidate;
@@ -83,6 +83,7 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->amreloptspecset = gingetreloptspecset;
 
 	PG_RETURN_POINTER(amroutine);
 }
@@ -605,21 +606,6 @@ ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
 	return entries;
 }
 
-bytea *
-ginoptions(Datum reloptions, bool validate)
-{
-	static const relopt_parse_elt tab[] = {
-		{"fastupdate", RELOPT_TYPE_BOOL, offsetof(GinOptions, useFastUpdate)},
-		{"gin_pending_list_limit", RELOPT_TYPE_INT, offsetof(GinOptions,
-															 pendingListCleanupSize)}
-	};
-
-	return (bytea *) build_reloptions(reloptions, validate,
-									  RELOPT_KIND_GIN,
-									  sizeof(GinOptions),
-									  tab, lengthof(tab));
-}
-
 /*
  * Fetch index's statistical data into *stats
  *
@@ -706,3 +692,29 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build)
 
 	END_CRIT_SECTION();
 }
+
+static options_spec_set *gin_relopt_specset = NULL;
+
+void *
+gingetreloptspecset(void)
+{
+	if (gin_relopt_specset)
+		return gin_relopt_specset;
+
+	gin_relopt_specset = allocateOptionsSpecSet(NULL,
+												sizeof(GinOptions), false, 2);
+
+	optionsSpecSetAddBool(gin_relopt_specset, "fastupdate",
+						  "Enables \"fast update\" feature for this GIN index",
+						  AccessExclusiveLock,
+						  offsetof(GinOptions, useFastUpdate), NULL,
+						  GIN_DEFAULT_USE_FASTUPDATE);
+
+	optionsSpecSetAddInt(gin_relopt_specset, "gin_pending_list_limit",
+						 "Maximum size of the pending list for this GIN index, in kilobytes",
+						 AccessExclusiveLock,
+						 offsetof(GinOptions, pendingListCleanupSize), NULL,
+						 -1, 64, MAX_KILOBYTES);
+
+	return gin_relopt_specset;
+}
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 8c6c744ab7..f7300f5c23 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -90,7 +90,6 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amvacuumcleanup = gistvacuumcleanup;
 	amroutine->amcanreturn = gistcanreturn;
 	amroutine->amcostestimate = gistcostestimate;
-	amroutine->amoptions = gistoptions;
 	amroutine->amproperty = gistproperty;
 	amroutine->ambuildphasename = NULL;
 	amroutine->amvalidate = gistvalidate;
@@ -105,6 +104,7 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->amreloptspecset = gistgetreloptspecset;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index d4bf0c7563..78c3f8976e 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -17,7 +17,7 @@
 
 #include "access/gist_private.h"
 #include "access/htup_details.h"
-#include "access/reloptions.h"
+#include "access/options.h"
 #include "catalog/pg_opclass.h"
 #include "common/pg_prng.h"
 #include "storage/indexfsm.h"
@@ -917,20 +917,6 @@ gistPageRecyclable(Page page)
 	return false;
 }
 
-bytea *
-gistoptions(Datum reloptions, bool validate)
-{
-	static const relopt_parse_elt tab[] = {
-		{"fillfactor", RELOPT_TYPE_INT, offsetof(GiSTOptions, fillfactor)},
-		{"buffering", RELOPT_TYPE_ENUM, offsetof(GiSTOptions, buffering_mode)}
-	};
-
-	return (bytea *) build_reloptions(reloptions, validate,
-									  RELOPT_KIND_GIST,
-									  sizeof(GiSTOptions),
-									  tab, lengthof(tab));
-}
-
 /*
  *	gistproperty() -- Check boolean properties of indexes.
  *
@@ -1065,3 +1051,39 @@ gistGetFakeLSN(Relation rel)
 		return GetFakeLSNForUnloggedRel();
 	}
 }
+
+/* values from GistOptBufferingMode */
+opt_enum_elt_def gistBufferingOptValues[] =
+{
+	{"auto", GIST_OPTION_BUFFERING_AUTO},
+	{"on", GIST_OPTION_BUFFERING_ON},
+	{"off", GIST_OPTION_BUFFERING_OFF},
+	{(const char *) NULL}		/* list terminator */
+};
+
+static options_spec_set *gist_relopt_specset = NULL;
+
+void *
+gistgetreloptspecset(void)
+{
+	if (gist_relopt_specset)
+		return gist_relopt_specset;
+
+	gist_relopt_specset = allocateOptionsSpecSet(NULL,
+												sizeof(GiSTOptions), false, 2);
+
+	optionsSpecSetAddInt(gist_relopt_specset, "fillfactor",
+						 "Packs gist index pages only to this percentage",
+						 NoLock,		/* No ALTER, no lock */
+						 offsetof(GiSTOptions, fillfactor), NULL,
+						 GIST_DEFAULT_FILLFACTOR, GIST_MIN_FILLFACTOR, 100);
+
+	optionsSpecSetAddEnum(gist_relopt_specset, "buffering",
+						  "Enables buffering build for this GiST index",
+						  NoLock,		/* No ALTER, no lock */
+						  offsetof(GiSTOptions, buffering_mode), NULL,
+						  gistBufferingOptValues,
+						  GIST_OPTION_BUFFERING_AUTO,
+						  gettext_noop("Valid values are \"on\", \"off\", and \"auto\"."));
+	return gist_relopt_specset;
+}
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index a259a301fa..31c9c77941 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -87,7 +87,6 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amvacuumcleanup = hashvacuumcleanup;
 	amroutine->amcanreturn = NULL;
 	amroutine->amcostestimate = hashcostestimate;
-	amroutine->amoptions = hashoptions;
 	amroutine->amproperty = NULL;
 	amroutine->ambuildphasename = NULL;
 	amroutine->amvalidate = hashvalidate;
@@ -102,6 +101,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->amreloptspecset = hashgetreloptspecset;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c
index edb6fa968f..a62fbcba93 100644
--- a/src/backend/access/hash/hashutil.c
+++ b/src/backend/access/hash/hashutil.c
@@ -15,7 +15,7 @@
 #include "postgres.h"
 
 #include "access/hash.h"
-#include "access/reloptions.h"
+#include "access/options.h"
 #include "access/relscan.h"
 #include "port/pg_bitutils.h"
 #include "storage/buf_internals.h"
@@ -272,19 +272,6 @@ _hash_checkpage(Relation rel, Buffer buf, int flags)
 	}
 }
 
-bytea *
-hashoptions(Datum reloptions, bool validate)
-{
-	static const relopt_parse_elt tab[] = {
-		{"fillfactor", RELOPT_TYPE_INT, offsetof(HashOptions, fillfactor)},
-	};
-
-	return (bytea *) build_reloptions(reloptions, validate,
-									  RELOPT_KIND_HASH,
-									  sizeof(HashOptions),
-									  tab, lengthof(tab));
-}
-
 /*
  * _hash_get_indextuple_hashkey - get the hash index tuple's hash key value
  */
@@ -620,3 +607,22 @@ _hash_kill_items(IndexScanDesc scan)
 	else
 		_hash_relbuf(rel, buf);
 }
+
+static options_spec_set *hash_relopt_specset = NULL;
+
+void *
+hashgetreloptspecset(void)
+{
+	if (hash_relopt_specset)
+		return hash_relopt_specset;
+
+	hash_relopt_specset = allocateOptionsSpecSet(NULL,
+												sizeof(HashOptions), false, 1);
+	optionsSpecSetAddInt(hash_relopt_specset, "fillfactor",
+						 "Packs hash index pages only to this percentage",
+						 NoLock,		/* No ALTER -- no lock */
+						 offsetof(HashOptions, fillfactor), NULL,
+						 HASH_DEFAULT_FILLFACTOR, HASH_MIN_FILLFACTOR, 100);
+
+	return hash_relopt_specset;
+}
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index c9b4964c1e..9930172037 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -23,6 +23,7 @@
 #include "access/relscan.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "access/options.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
 #include "miscadmin.h"
@@ -126,7 +127,6 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amvacuumcleanup = btvacuumcleanup;
 	amroutine->amcanreturn = btcanreturn;
 	amroutine->amcostestimate = btcostestimate;
-	amroutine->amoptions = btoptions;
 	amroutine->amproperty = btproperty;
 	amroutine->ambuildphasename = btbuildphasename;
 	amroutine->amvalidate = btvalidate;
@@ -141,6 +141,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = btestimateparallelscan;
 	amroutine->aminitparallelscan = btinitparallelscan;
 	amroutine->amparallelrescan = btparallelrescan;
+	amroutine->amreloptspecset = btgetreloptspecset;
 
 	PG_RETURN_POINTER(amroutine);
 }
@@ -1420,3 +1421,35 @@ btcanreturn(Relation index, int attno)
 {
 	return true;
 }
+
+static options_spec_set *bt_relopt_specset = NULL;
+
+void *
+btgetreloptspecset(void)
+{
+	if (bt_relopt_specset)
+		return bt_relopt_specset;
+
+	bt_relopt_specset = allocateOptionsSpecSet(NULL,
+											   sizeof(BTOptions), false, 3);
+
+	optionsSpecSetAddInt(bt_relopt_specset, "fillfactor",
+						 "Packs btree index pages only to this percentage",
+						 ShareUpdateExclusiveLock, /* affects inserts only */
+						 offsetof(BTOptions, fillfactor), NULL,
+						 BTREE_DEFAULT_FILLFACTOR, BTREE_MIN_FILLFACTOR, 100);
+
+	optionsSpecSetAddReal(bt_relopt_specset, "vacuum_cleanup_index_scale_factor",
+						 "Number of tuple inserts prior to index cleanup as a fraction of reltuples",
+						 ShareUpdateExclusiveLock,
+						 offsetof(BTOptions,vacuum_cleanup_index_scale_factor),
+						 NULL, -1, 0.0, 1e10);
+
+	optionsSpecSetAddBool(bt_relopt_specset, "deduplicate_items",
+						  "Enables \"deduplicate items\" feature for this btree index",
+						  ShareUpdateExclusiveLock, /* affects inserts only */
+						  offsetof(BTOptions,deduplicate_items), NULL,
+						  true);
+
+	return bt_relopt_specset;
+}
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 6a651d8397..65db3c0acc 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -18,7 +18,7 @@
 #include <time.h>
 
 #include "access/nbtree.h"
-#include "access/reloptions.h"
+#include "storage/lock.h"
 #include "access/relscan.h"
 #include "catalog/catalog.h"
 #include "commands/progress.h"
@@ -2107,25 +2107,6 @@ BTreeShmemInit(void)
 		Assert(found);
 }
 
-bytea *
-btoptions(Datum reloptions, bool validate)
-{
-	static const relopt_parse_elt tab[] = {
-		{"fillfactor", RELOPT_TYPE_INT, offsetof(BTOptions, fillfactor)},
-		{"vacuum_cleanup_index_scale_factor", RELOPT_TYPE_REAL,
-		offsetof(BTOptions, vacuum_cleanup_index_scale_factor)},
-		{"deduplicate_items", RELOPT_TYPE_BOOL,
-		offsetof(BTOptions, deduplicate_items)}
-
-	};
-
-	return (bytea *) build_reloptions(reloptions, validate,
-									  RELOPT_KIND_BTREE,
-									  sizeof(BTOptions),
-									  tab, lengthof(tab));
-
-}
-
 /*
  *	btproperty() -- Check boolean properties of indexes.
  *
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index 1ae7492216..1cec98c3a9 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -17,7 +17,7 @@
 
 #include "access/amvalidate.h"
 #include "access/htup_details.h"
-#include "access/reloptions.h"
+#include "access/options.h"
 #include "access/spgist_private.h"
 #include "access/toast_compression.h"
 #include "access/transam.h"
@@ -74,7 +74,6 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amvacuumcleanup = spgvacuumcleanup;
 	amroutine->amcanreturn = spgcanreturn;
 	amroutine->amcostestimate = spgcostestimate;
-	amroutine->amoptions = spgoptions;
 	amroutine->amproperty = spgproperty;
 	amroutine->ambuildphasename = NULL;
 	amroutine->amvalidate = spgvalidate;
@@ -89,6 +88,7 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->amreloptspecset = spggetreloptspecset;
 
 	PG_RETURN_POINTER(amroutine);
 }
@@ -733,23 +733,6 @@ SpGistInitMetapage(Page page)
 		((char *) metadata + sizeof(SpGistMetaPageData)) - (char *) page;
 }
 
-/*
- * reloptions processing for SPGiST
- */
-bytea *
-spgoptions(Datum reloptions, bool validate)
-{
-	static const relopt_parse_elt tab[] = {
-		{"fillfactor", RELOPT_TYPE_INT, offsetof(SpGistOptions, fillfactor)},
-	};
-
-	return (bytea *) build_reloptions(reloptions, validate,
-									  RELOPT_KIND_SPGIST,
-									  sizeof(SpGistOptions),
-									  tab, lengthof(tab));
-
-}
-
 /*
  * Get the space needed to store a non-null datum of the indicated type
  * in an inner tuple (that is, as a prefix or node label).
@@ -1349,3 +1332,24 @@ spgproperty(Oid index_oid, int attno,
 
 	return true;
 }
+
+static options_spec_set *spgist_relopt_specset = NULL;
+
+void *
+spggetreloptspecset(void)
+{
+
+	if (spgist_relopt_specset)
+		return spgist_relopt_specset;
+
+	spgist_relopt_specset = allocateOptionsSpecSet(NULL,
+											  sizeof(SpGistOptions), false, 1);
+
+	optionsSpecSetAddInt(spgist_relopt_specset, "fillfactor",
+						"Packs spgist index pages only to this percentage",
+						ShareUpdateExclusiveLock,	/* affects only inserts */
+						offsetof(SpGistOptions, fillfactor), NULL,
+						SPGIST_DEFAULT_FILLFACTOR, SPGIST_MIN_FILLFACTOR, 100);
+
+	return spgist_relopt_specset;
+}
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 9abbb6b555..53885088ab 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -90,6 +90,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	Datum		toast_options;
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
 	ObjectAddress intoRelationAddr;
+	List	   *toastDefList;
 
 	/* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */
 	is_matview = (into->viewQuery != NULL);
@@ -124,14 +125,12 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	CommandCounterIncrement();
 
 	/* parse and validate reloptions for the toast table */
-	toast_options = transformRelOptions((Datum) 0,
-										create->options,
-										"toast",
-										validnsps,
-										true, false);
 
-	(void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
+	optionsDefListValdateNamespaces(create->options, validnsps);
+	toastDefList = optionsDefListFilterNamespaces(create->options, "toast");
 
+	toast_options = optionDefListToTextArray(get_toast_relopt_spec_set(),
+																toastDefList);
 	NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options);
 
 	/* Create the "view" part of a materialized view. */
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index ea27857bb8..13e50cd08e 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -112,7 +112,7 @@ transformGenericOptions(Oid catalogId,
 						List *options,
 						Oid fdwvalidator)
 {
-	List	   *resultOptions = untransformRelOptions(oldOptions);
+	List	   *resultOptions = optionsTextArrayToDefList(oldOptions);
 	ListCell   *optcell;
 	Datum		result;
 
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 560dcc87a2..48e7ac938f 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -19,6 +19,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/reloptions.h"
+#include "access/options.h"
 #include "access/sysattr.h"
 #include "access/tableam.h"
 #include "access/xact.h"
@@ -531,7 +532,7 @@ DefineIndex(Oid relationId,
 	Form_pg_am	accessMethodForm;
 	IndexAmRoutine *amRoutine;
 	bool		amcanorder;
-	amoptions_function amoptions;
+	amreloptspecset_function amreloptspecsetfn;
 	bool		partitioned;
 	bool		safe_index;
 	Datum		reloptions;
@@ -837,7 +838,7 @@ DefineIndex(Oid relationId,
 						accessMethodName)));
 
 	amcanorder = amRoutine->amcanorder;
-	amoptions = amRoutine->amoptions;
+	amreloptspecsetfn = amRoutine->amreloptspecset;
 
 	pfree(amRoutine);
 	ReleaseSysCache(tuple);
@@ -851,10 +852,17 @@ DefineIndex(Oid relationId,
 	/*
 	 * Parse AM-specific options, convert to text array form, validate.
 	 */
-	reloptions = transformRelOptions((Datum) 0, stmt->options,
-									 NULL, NULL, false, false);
 
-	(void) index_reloptions(amoptions, reloptions, true);
+	if (stmt->options && ! amreloptspecsetfn)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("access method %s does not support options",
+						accessMethodName)));
+	}
+	if (amreloptspecsetfn)
+		reloptions = optionDefListToTextArray(amreloptspecsetfn(),
+															stmt->options);
 
 	/*
 	 * Prepare arguments for index_create, primarily an IndexInfo structure.
@@ -1987,8 +1995,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 					palloc0(sizeof(Datum) * indexInfo->ii_NumIndexAttrs);
 
 			indexInfo->ii_OpclassOptions[attn] =
-				transformRelOptions((Datum) 0, attribute->opclassopts,
-									NULL, NULL, false, false);
+				optionsDefListToTextArray(attribute->opclassopts);
 		}
 
 		attn++;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3e83f375b5..e3e3cb70c6 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"
@@ -664,7 +665,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;
@@ -812,19 +812,36 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
-	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
-									 true, false);
 
 	switch (relkind)
 	{
 		case RELKIND_VIEW:
-			(void) view_reloptions(reloptions, true);
+			reloptions = optionDefListToTextArray(get_view_relopt_spec_set(),
+																stmt->options);
 			break;
 		case RELKIND_PARTITIONED_TABLE:
-			(void) partitioned_table_reloptions(reloptions, true);
+		{
+			/* If it is not all listed above, then it if heap */
+			char	   *namespaces[] = HEAP_RELOPT_NAMESPACES;
+			List	   *heapDefList;
+
+			optionsDefListValdateNamespaces(stmt->options, namespaces);
+			heapDefList = optionsDefListFilterNamespaces(stmt->options, NULL);
+			reloptions = optionDefListToTextArray(
+							   get_partitioned_relopt_spec_set(), heapDefList);
 			break;
+		}
 		default:
-			(void) heap_reloptions(relkind, reloptions, true);
+		{
+			/* If it is not all listed above, is should be heap */
+			char	   *namespaces[] = HEAP_RELOPT_NAMESPACES;
+			List	   *heapDefList;
+
+			optionsDefListValdateNamespaces(stmt->options, namespaces);
+			heapDefList = optionsDefListFilterNamespaces(stmt->options, NULL);
+			reloptions = optionDefListToTextArray(get_heap_relopt_spec_set(),
+																  heapDefList);
+		}
 	}
 
 	if (stmt->ofTypename)
@@ -4077,7 +4094,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);
 
@@ -4119,7 +4136,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.
@@ -4340,9 +4357,13 @@ AlterTableGetLockLevel(List *cmds)
 									 * getTables() */
 			case AT_ResetRelOptions:	/* Uses MVCC in getIndexes() and
 										 * getTables() */
-				cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
-				break;
-
+				{
+					Relation rel = relation_open(relid, NoLock);  // FIXME I am not sure how wise it is
+					cmd_lockmode = AlterTableGetRelOptionsLockLevel(rel,
+													castNode(List, cmd->def));
+					relation_close(rel,NoLock);
+					break;
+				}
 			case AT_AttachPartition:
 				cmd_lockmode = ShareUpdateExclusiveLock;
 				break;
@@ -8131,12 +8152,13 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 	/* Generate new proposed attoptions (text array) */
 	datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
 							&isnull);
-	newOptions = transformRelOptions(isnull ? (Datum) 0 : datum,
-									 castNode(List, options), NULL, NULL,
-									 false, isReset);
-	/* Validate new options */
-	(void) attribute_reloptions(newOptions, true);
+	if (isnull)
+		datum = (Datum) 0;
 
+	newOptions = optionsUpdateTexArrayWithDefList(
+											get_attribute_options_spec_set(),
+											datum, castNode(List, options),
+											isReset);
 	/* Build new tuple. */
 	memset(repl_null, false, sizeof(repl_null));
 	memset(repl_repl, false, sizeof(repl_repl));
@@ -14092,12 +14114,13 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	HeapTuple	tuple;
 	HeapTuple	newtuple;
 	Datum		datum;
-	bool		isnull;
 	Datum		newOptions;
 	Datum		repl_val[Natts_pg_class];
 	bool		repl_null[Natts_pg_class];
 	bool		repl_repl[Natts_pg_class];
-	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+	List	   *optionsDefList;
+	options_spec_set *optionsSpecSet;
+	char	   *heap_namespaces[] = HEAP_RELOPT_NAMESPACES;
 
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
@@ -14117,38 +14140,58 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 		 * there were none before.
 		 */
 		datum = (Datum) 0;
-		isnull = true;
 	}
 	else
 	{
+		bool		isnull;
 		/* Get the old reloptions */
 		datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions,
 								&isnull);
+		if (isnull)
+			datum = (Datum) 0;
 	}
 
 	/* Generate new proposed reloptions (text array) */
-	newOptions = transformRelOptions(isnull ? (Datum) 0 : datum,
-									 defList, NULL, validnsps, false,
-									 operation == AT_ResetRelOptions);
 
 	/* Validate */
+
+	optionsSpecSet = NULL;
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
-		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
-			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
+			optionsDefListValdateNamespaces(defList, heap_namespaces);
+			optionsDefList = optionsDefListFilterNamespaces(defList, NULL);
+			optionsSpecSet = get_heap_relopt_spec_set();
 			break;
 		case RELKIND_PARTITIONED_TABLE:
-			(void) partitioned_table_reloptions(newOptions, true);
+			optionsDefListValdateNamespaces(defList, heap_namespaces);
+			optionsDefList = optionsDefListFilterNamespaces(defList, NULL);
+			optionsSpecSet = get_partitioned_relopt_spec_set();
 			break;
 		case RELKIND_VIEW:
-			(void) view_reloptions(newOptions, true);
+			optionsDefList = defList;
+			optionsSpecSet = get_view_relopt_spec_set();
 			break;
 		case RELKIND_INDEX:
 		case RELKIND_PARTITIONED_INDEX:
-			(void) index_reloptions(rel->rd_indam->amoptions, newOptions, true);
+			if (! rel->rd_indam->amreloptspecset)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("index %s does not support options",
+								RelationGetRelationName(rel))));
+				break;
+			}
+			optionsDefList = defList;
+			optionsSpecSet = rel->rd_indam->amreloptspecset();
 			break;
+		case RELKIND_TOASTVALUE:
+			/* Should never get here */
+			/* TOAST options are never altered directly */
+			Assert(0);
+			/* FALLTHRU */
+			/* If we get here in prod. error is the best option */
 		default:
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -14158,11 +14201,15 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 			break;
 	}
 
+	newOptions = optionsUpdateTexArrayWithDefList(optionsSpecSet, datum,
+												  optionsDefList,
+											  operation == AT_ResetRelOptions);
+
 	/* Special-case validation of view options */
 	if (rel->rd_rel->relkind == RELKIND_VIEW)
 	{
 		Query	   *view_query = get_view_query(rel);
-		List	   *view_options = untransformRelOptions(newOptions);
+		List	   *view_options = optionsTextArrayToDefList(newOptions);
 		ListCell   *cell;
 		bool		check_option = false;
 
@@ -14237,20 +14284,22 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 			 * pretend there were none before.
 			 */
 			datum = (Datum) 0;
-			isnull = true;
 		}
 		else
 		{
+			bool		isnull;
 			/* Get the old reloptions */
 			datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions,
 									&isnull);
+			if (isnull)
+				datum = (Datum) 0;
 		}
 
-		newOptions = transformRelOptions(isnull ? (Datum) 0 : datum,
-										 defList, "toast", validnsps, false,
-										 operation == AT_ResetRelOptions);
-
-		(void) heap_reloptions(RELKIND_TOASTVALUE, newOptions, true);
+		optionsDefList = optionsDefListFilterNamespaces(defList, "toast");
+		newOptions = optionsUpdateTexArrayWithDefList(
+												   get_toast_relopt_spec_set(),
+												   datum, optionsDefList,
+											  operation == AT_ResetRelOptions);
 
 		memset(repl_val, 0, sizeof(repl_val));
 		memset(repl_null, false, sizeof(repl_null));
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index b2ccf5e06e..a883d89c14 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -363,10 +363,9 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
 	nulls[Anum_pg_tablespace_spcacl - 1] = true;
 
 	/* Generate new proposed spcoptions (text array) */
-	newOptions = transformRelOptions((Datum) 0,
-									 stmt->options,
-									 NULL, NULL, false, false);
-	(void) tablespace_reloptions(newOptions, true);
+	newOptions = optionDefListToTextArray(get_tablespace_options_spec_set(),
+																stmt->options);
+
 	if (newOptions != (Datum) 0)
 		values[Anum_pg_tablespace_spcoptions - 1] = newOptions;
 	else
@@ -1091,11 +1090,12 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 	/* Generate new proposed spcoptions (text array) */
 	datum = heap_getattr(tup, Anum_pg_tablespace_spcoptions,
 						 RelationGetDescr(rel), &isnull);
-	newOptions = transformRelOptions(isnull ? (Datum) 0 : datum,
-									 stmt->options, NULL, NULL, false,
-									 stmt->isReset);
-	(void) tablespace_reloptions(newOptions, true);
+	if (isnull)
+		datum = (Datum) 0;
 
+	newOptions = optionsUpdateTexArrayWithDefList(
+										  get_tablespace_options_spec_set(),
+										  datum, stmt->options, stmt->isReset);
 	/* Build new tuple. */
 	memset(repl_null, false, sizeof(repl_null));
 	memset(repl_repl, false, sizeof(repl_repl));
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index 294e22c78c..c60ed798b8 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 99efa26ce4..5ac6b9b99d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1758,7 +1758,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx,
 		/* Add the operator class name, if non-default */
 		iparam->opclass = get_opclass(indclass->values[keyno], keycoltype);
 		iparam->opclassopts =
-			untransformRelOptions(get_attoptions(source_relid, keyno + 1));
+			optionsTextArrayToDefList(get_attoptions(source_relid, keyno + 1));
 
 		iparam->ordering = SORTBY_DEFAULT;
 		iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
@@ -1822,7 +1822,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx,
 	datum = SysCacheGetAttr(RELOID, ht_idxrel,
 							Anum_pg_class_reloptions, &isnull);
 	if (!isnull)
-		index->options = untransformRelOptions(datum);
+		index->options = optionsTextArrayToDefList(datum);
 
 	/* If it's a partial index, decompile and append the predicate */
 	datum = SysCacheGetAttr(INDEXRELID, ht_idx,
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 83e4e37c78..df3f85de42 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1156,6 +1156,7 @@ ProcessUtilitySlow(ParseState *pstate,
 							CreateStmt *cstmt = (CreateStmt *) stmt;
 							Datum		toast_options;
 							static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+							List	   *toastDefList;
 
 							/* Remember transformed RangeVar for LIKE */
 							table_rv = cstmt->relation;
@@ -1179,15 +1180,16 @@ ProcessUtilitySlow(ParseState *pstate,
 							 * parse and validate reloptions for the toast
 							 * table
 							 */
-							toast_options = transformRelOptions((Datum) 0,
-																cstmt->options,
-																"toast",
-																validnsps,
-																true,
-																false);
-							(void) heap_reloptions(RELKIND_TOASTVALUE,
-												   toast_options,
-												   true);
+
+							optionsDefListValdateNamespaces(
+											  ((CreateStmt *) stmt)->options,
+															validnsps);
+
+							toastDefList = optionsDefListFilterNamespaces(
+									((CreateStmt *) stmt)->options, "toast");
+
+							toast_options = optionDefListToTextArray(
+									get_toast_relopt_spec_set(), toastDefList);
 
 							NewRelationCreateToastTable(address.objectId,
 														toast_options);
@@ -1296,9 +1298,12 @@ ProcessUtilitySlow(ParseState *pstate,
 					 * lock on (for example) a relation on which we have no
 					 * permissions.
 					 */
-					lockmode = AlterTableGetLockLevel(atstmt->cmds);
-					relid = AlterTableLookupRelation(atstmt, lockmode);
-
+					relid = AlterTableLookupRelation(atstmt, NoLock); // FIXME!
+					if (OidIsValid(relid))
+					{
+						lockmode = AlterTableGetLockLevel(relid, atstmt->cmds);
+						relid = AlterTableLookupRelation(atstmt, lockmode);
+					}
 					if (OidIsValid(relid))
 					{
 						AlterTableUtilityContext atcontext;
diff --git a/src/backend/utils/cache/attoptcache.c b/src/backend/utils/cache/attoptcache.c
index 9e252a0891..5ad32a8773 100644
--- a/src/backend/utils/cache/attoptcache.c
+++ b/src/backend/utils/cache/attoptcache.c
@@ -16,6 +16,7 @@
  */
 #include "postgres.h"
 
+#include "access/options.h"
 #include "access/reloptions.h"
 #include "utils/attoptcache.h"
 #include "utils/catcache.h"
@@ -148,7 +149,8 @@ get_attribute_options(Oid attrelid, int attnum)
 				opts = NULL;
 			else
 			{
-				bytea	   *bytea_opts = attribute_reloptions(datum, false);
+				bytea *bytea_opts = optionsTextArrayToBytea(
+								   get_attribute_options_spec_set(), datum, 0);
 
 				opts = MemoryContextAlloc(CacheMemoryContext,
 										  VARSIZE(bytea_opts));
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2707fed12f..5dfa6020a3 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -459,7 +459,7 @@ static void
 RelationParseRelOptions(Relation relation, HeapTuple tuple)
 {
 	bytea	   *options;
-	amoptions_function amoptsfn;
+	amreloptspecset_function amoptspecsetfn;
 
 	relation->rd_options = NULL;
 
@@ -474,11 +474,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;
@@ -489,7 +489,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 	 * we might not have any other for pg_class yet (consider executing this
 	 * code for pg_class itself)
 	 */
-	options = extractRelOptions(tuple, GetPgClassDescriptor(), amoptsfn);
+	options = extractRelOptions(tuple, GetPgClassDescriptor(), amoptspecsetfn);
 
 	/*
 	 * Copy parsed data into CacheMemoryContext.  To guard against the
diff --git a/src/backend/utils/cache/spccache.c b/src/backend/utils/cache/spccache.c
index 5609246c2f..95dac315f0 100644
--- a/src/backend/utils/cache/spccache.c
+++ b/src/backend/utils/cache/spccache.c
@@ -148,7 +148,8 @@ get_tablespace(Oid spcid)
 			opts = NULL;
 		else
 		{
-			bytea	   *bytea_opts = tablespace_reloptions(datum, false);
+			bytea *bytea_opts  = optionsTextArrayToBytea(
+								get_tablespace_options_spec_set(), datum, 0);
 
 			opts = MemoryContextAlloc(CacheMemoryContext, VARSIZE(bytea_opts));
 			memcpy(opts, bytea_opts, VARSIZE(bytea_opts));
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index a382551a98..833a5259ee 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -136,10 +136,6 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root,
 										 double *indexCorrelation,
 										 double *indexPages);
 
-/* parse index reloptions */
-typedef bytea *(*amoptions_function) (Datum reloptions,
-									  bool validate);
-
 /* report AM, index, or index column property */
 typedef bool (*amproperty_function) (Oid index_oid, int attno,
 									 IndexAMProperty prop, const char *propname,
@@ -186,6 +182,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan);
 /* restore marked scan position */
 typedef void (*amrestrpos_function) (IndexScanDesc scan);
 
+/* get catalog of reloptions definitions */
+typedef void *(*amreloptspecset_function) ();
+
 /*
  * Callback function signatures - for parallel index scans.
  */
@@ -265,7 +264,6 @@ typedef struct IndexAmRoutine
 	amvacuumcleanup_function amvacuumcleanup;
 	amcanreturn_function amcanreturn;	/* can be NULL */
 	amcostestimate_function amcostestimate;
-	amoptions_function amoptions;
 	amproperty_function amproperty; /* can be NULL */
 	ambuildphasename_function ambuildphasename; /* can be NULL */
 	amvalidate_function amvalidate;
@@ -277,6 +275,7 @@ typedef struct IndexAmRoutine
 	amendscan_function amendscan;
 	ammarkpos_function ammarkpos;	/* can be NULL */
 	amrestrpos_function amrestrpos; /* can be NULL */
+	amreloptspecset_function amreloptspecset; /* can be NULL */
 
 	/* interface functions to support parallel index scans */
 	amestimateparallelscan_function amestimateparallelscan; /* can be NULL */
diff --git a/src/include/access/brin.h b/src/include/access/brin.h
index 887fb0a553..0aaf118ea5 100644
--- a/src/include/access/brin.h
+++ b/src/include/access/brin.h
@@ -36,6 +36,8 @@ typedef struct BrinStatsData
 
 
 #define BRIN_DEFAULT_PAGES_PER_RANGE	128
+#define BRIN_MIN_PAGES_PER_RANGE		1
+#define BRIN_MAX_PAGES_PER_RANGE		131072
 #define BrinGetPagesPerRange(relation) \
 	(AssertMacro(relation->rd_rel->relkind == RELKIND_INDEX && \
 				 relation->rd_rel->relam == BRIN_AM_OID), \
diff --git a/src/include/access/brin_internal.h b/src/include/access/brin_internal.h
index 2518660927..e25ed4269b 100644
--- a/src/include/access/brin_internal.h
+++ b/src/include/access/brin_internal.h
@@ -14,6 +14,7 @@
 #include "access/amapi.h"
 #include "storage/bufpage.h"
 #include "utils/typcache.h"
+#include "access/options.h"
 
 
 /*
@@ -108,6 +109,7 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info,
 extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info,
 												IndexBulkDeleteResult *stats);
 extern bytea *brinoptions(Datum reloptions, bool validate);
+extern void * bringetreloptspecset (void);
 
 /* brin_validate.c */
 extern bool brinvalidate(Oid opclassoid);
diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h
index 2935d2f353..e69fd6fd52 100644
--- a/src/include/access/gin_private.h
+++ b/src/include/access/gin_private.h
@@ -108,6 +108,7 @@ extern Datum *ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
 extern OffsetNumber gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple);
 extern Datum gintuple_get_key(GinState *ginstate, IndexTuple tuple,
 							  GinNullCategory *category);
+extern void *gingetreloptspecset(void);
 
 /* gininsert.c */
 extern IndexBuildResult *ginbuild(Relation heap, Relation index,
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 240131ef71..7e586061e7 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -22,6 +22,7 @@
 #include "storage/buffile.h"
 #include "utils/hsearch.h"
 #include "access/genam.h"
+#include "access/options.h"
 
 /*
  * Maximum number of "halves" a page can be split into in one operation.
@@ -388,6 +389,7 @@ typedef enum GistOptBufferingMode
 	GIST_OPTION_BUFFERING_OFF
 } GistOptBufferingMode;
 
+
 /*
  * Storage type for GiST's reloptions
  */
@@ -478,7 +480,7 @@ extern void gistadjustmembers(Oid opfamilyoid,
 #define GIST_MIN_FILLFACTOR			10
 #define GIST_DEFAULT_FILLFACTOR		90
 
-extern bytea *gistoptions(Datum reloptions, bool validate);
+extern void *gistgetreloptspecset(void);
 extern bool gistproperty(Oid index_oid, int attno,
 						 IndexAMProperty prop, const char *propname,
 						 bool *res, bool *isnull);
diff --git a/src/include/access/hash.h b/src/include/access/hash.h
index cd7b2a53d8..58468249de 100644
--- a/src/include/access/hash.h
+++ b/src/include/access/hash.h
@@ -378,7 +378,6 @@ extern IndexBulkDeleteResult *hashbulkdelete(IndexVacuumInfo *info,
 											 void *callback_state);
 extern IndexBulkDeleteResult *hashvacuumcleanup(IndexVacuumInfo *info,
 												IndexBulkDeleteResult *stats);
-extern bytea *hashoptions(Datum reloptions, bool validate);
 extern bool hashvalidate(Oid opclassoid);
 extern void hashadjustmembers(Oid opfamilyoid,
 							  Oid opclassoid,
@@ -470,6 +469,7 @@ extern BlockNumber _hash_get_newblock_from_oldbucket(Relation rel, Bucket old_bu
 extern Bucket _hash_get_newbucket_from_oldbucket(Relation rel, Bucket old_bucket,
 												 uint32 lowmask, uint32 maxbucket);
 extern void _hash_kill_items(IndexScanDesc scan);
+extern void *hashgetreloptspecset(void);
 
 /* hash.c */
 extern void hashbucketcleanup(Relation rel, Bucket cur_bucket,
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 9fec6fb1a8..7d3e2a600c 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -1252,7 +1252,7 @@ extern void _bt_end_vacuum(Relation rel);
 extern void _bt_end_vacuum_callback(int code, Datum arg);
 extern Size BTreeShmemSize(void);
 extern void BTreeShmemInit(void);
-extern bytea *btoptions(Datum reloptions, bool validate);
+extern void * btgetreloptspecset (void);
 extern bool btproperty(Oid index_oid, int attno,
 					   IndexAMProperty prop, const char *propname,
 					   bool *res, bool *isnull);
diff --git a/src/include/access/options.h b/src/include/access/options.h
new file mode 100644
index 0000000000..3600d41e4d
--- /dev/null
+++ b/src/include/access/options.h
@@ -0,0 +1,261 @@
+/*-------------------------------------------------------------------------
+ *
+ * options.h
+ *	  An uniform, context-free API for processing name=value options. Used
+ *	  to process relation options (reloptions), attribute options, opclass
+ *	  options, etc.
+ *
+ * Note: the functions dealing with text-array options values declare
+ * them as Datum, not ArrayType *, to avoid needing to include array.h
+ * into a lot of low-level code.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * src/include/access/options.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OPTIONS_H
+#define OPTIONS_H
+
+#include "storage/lock.h"
+#include "nodes/pg_list.h"
+
+
+/* supported option types */
+typedef enum option_type
+{
+	OPTION_TYPE_BOOL,
+	OPTION_TYPE_INT,
+	OPTION_TYPE_REAL,
+	OPTION_TYPE_ENUM,
+	OPTION_TYPE_STRING
+}	option_type;
+
+typedef enum option_value_status
+{
+	OPTION_VALUE_STATUS_EMPTY,	/* Option was just initialized */
+	OPTION_VALUE_STATUS_RAW,	/* Option just came from syntax analyzer in
+								 * has name, and raw (unparsed) value */
+	OPTION_VALUE_STATUS_PARSED, /* Option was parsed and has link to catalog
+								 * entry and proper value */
+	OPTION_VALUE_STATUS_FOR_RESET		/* This option came from ALTER xxx
+										 * RESET */
+}	option_value_status;
+
+/*
+ * opt_enum_elt_def -- One member of the array of acceptable values
+ * of an enum reloption.
+ */
+typedef struct opt_enum_elt_def
+{
+	const char *string_val;
+	int			symbol_val;
+} opt_enum_elt_def;
+
+
+typedef struct option_value option_value;
+
+/* Function that would be called after option validation.
+ * Might be needed for custom warnings, errors, or for changing
+ * option value after being validated, etc.
+ */
+typedef void (*option_value_postvalidate) (option_value* value);
+
+/* generic structure to store Option Spec information */
+typedef struct option_spec_basic
+{
+	const char *name;	/* must be first (used as list termination marker) */
+	const char *desc;
+	LOCKMODE	lockmode;
+	option_type type;
+	int			struct_offset; /* offset of the value in Bytea representation */
+	option_value_postvalidate	postvalidate_fn;
+}	option_spec_basic;
+
+
+/* reloptions records for specific variable types */
+typedef struct option_spec_bool
+{
+	option_spec_basic base;
+	bool		default_val;
+}	option_spec_bool;
+
+typedef struct option_spec_int
+{
+	option_spec_basic base;
+	int			default_val;
+	int			min;
+	int			max;
+}	option_spec_int;
+
+typedef struct option_spec_real
+{
+	option_spec_basic base;
+	double		default_val;
+	double		min;
+	double		max;
+}	option_spec_real;
+
+typedef struct option_spec_enum
+{
+	option_spec_basic base;
+	opt_enum_elt_def *members;	/* Null terminated array of allowed names and
+								 * corresponding values */
+	int			default_val;	/* Default value, may differ from values in
+								 * members array */
+	const char  *detailmsg;
+}	option_spec_enum;
+
+/* validation routines for strings */
+typedef void (*validate_string_option) (const char *value);
+typedef Size (*fill_string_option) (const char *value, void *ptr);
+
+/*
+ * When storing sting reloptions, we should deal with special case when
+ * option value is not set. For fixed length options, we just copy default
+ * option value into the binary structure. For varlen value, there can be
+ * "not set" special case, with no default value offered.
+ * In this case we will set offset value to -1, so code that use reloptions
+ * can deal this case. For better readability it was defined as a constant.
+ */
+#define OPTION_STRING_VALUE_NOT_SET_OFFSET -1
+
+typedef struct option_spec_string
+{
+	option_spec_basic base;
+	validate_string_option validate_cb;
+	fill_string_option fill_cb;
+	char	   *default_val;
+}	option_spec_string;
+
+typedef void (*postprocess_bytea_options_function) (void *data, bool validate);
+
+typedef struct options_spec_set
+{
+	option_spec_basic **definitions;
+	int			num;			/* Number of spec_set items in use */
+	int			num_allocated;	/* Number of spec_set items allocated */
+	bool		forbid_realloc; /* If number of items of the spec_set were
+								 * strictly set to certain value do no allow
+								 * adding more idems */
+	bool		is_local;		/* If true specset is in local memory context */
+	Size		struct_size;	/* Size of a structure for options in binary
+								 * representation */
+	postprocess_bytea_options_function postprocess_fun; /* This function is
+														 * called after options
+														 * were converted in
+														 * Bytea representation.
+														 * Can be used for extra
+														 * validation etc. */
+	char	   *namespace;		/* spec_set is used for options from this
+								 * namespase */
+}	options_spec_set;
+
+
+/* holds an option value parsed or unparsed */
+typedef struct option_value
+{
+	option_spec_basic *gen;
+	char	   *namespace;
+	option_value_status status;
+	char	   *raw_value;		/* allocated separately */
+	char	   *raw_name;
+	union
+	{
+		bool		bool_val;
+		int			int_val;
+		double		real_val;
+		int			enum_val;
+		char	   *string_val; /* allocated separately */
+	}			values;
+}	option_value;
+
+
+/*
+ * Options spec_set related functions
+ */
+extern options_spec_set *allocateOptionsSpecSet(const char *namespace,
+						int bytea_size, bool is_local, int num_items_expected);
+
+extern void optionsSpecSetAddBool(options_spec_set *spec_set, const char *name,
+								  const char *desc, LOCKMODE lockmode,
+								  int struct_offset,
+								  option_value_postvalidate postvalidate_fn,
+								  bool default_val);
+
+extern void optionsSpecSetAddInt(options_spec_set *spec_set, const char *name,
+								 const char *desc, LOCKMODE lockmode,
+								 int struct_offset,
+								 option_value_postvalidate postvalidate_fn,
+								 int default_val, int min_val, int max_val);
+
+extern void optionsSpecSetAddReal(options_spec_set *spec_set, const char *name,
+							const char *desc, LOCKMODE lockmode,
+							int struct_offset,
+							option_value_postvalidate postvalidate_fn,
+							double default_val, double min_val, double max_val);
+
+extern void optionsSpecSetAddEnum(options_spec_set *spec_set, const char *name,
+								  const char *desc, LOCKMODE lockmode,
+								  int struct_offset,
+								  option_value_postvalidate postvalidate_fn,
+								  opt_enum_elt_def* members, int default_val,
+								  const char *detailmsg);
+
+
+extern void optionsSpecSetAddString(options_spec_set *spec_set,
+									const char *name, const char *desc,
+									LOCKMODE lockmode, int struct_offset,
+									option_value_postvalidate postvalidate_fn,
+									const char *default_val,
+									validate_string_option validator,
+									fill_string_option filler);
+
+
+/*
+ * This macro allows to get string option value from bytea representation.
+ * "optstruct" - is a structure that is stored in bytea options representation
+ * "member" - member of this structure that has string option value
+ * (actually string values are stored in bytea after the structure, and
+ * and "member" will contain an offset to this value. This macro do all
+ * the math
+ */
+#define GET_STRING_OPTION(optstruct, member) \
+	((optstruct)->member == OPTION_STRING_VALUE_NOT_SET_OFFSET ? NULL : \
+	 (char *)(optstruct) + (optstruct)->member)
+
+/*
+ * Functions related to option conversion, parsing, manipulation
+ * and validation
+ */
+extern void optionsDefListValdateNamespaces(List *defList,
+													char **allowed_namespaces);
+extern List *optionsDefListFilterNamespaces(List *defList,
+														const char *namespace);
+extern List *optionsTextArrayToDefList(Datum options);
+extern Datum optionsDefListToTextArray(List *defList);
+/*
+ * Meta functions that uses functions above to get options for relations,
+ * tablespaces, views and so on
+ */
+
+extern bytea *optionsTextArrayToBytea(options_spec_set * spec_set, Datum data,
+																bool validate);
+extern Datum optionsUpdateTexArrayWithDefList(options_spec_set * spec_set,
+								Datum oldOptions, List *defList, bool do_reset);
+extern Datum optionDefListToTextArray(options_spec_set * spec_set,
+																List *defList);
+
+/* Internal functions */
+
+extern List *optionsTextArrayToRawValues(Datum array_datum);
+extern List *optionsParseRawValues(List *raw_values,
+								   options_spec_set * spec_set,	bool validate);
+extern bytea *optionsValuesToBytea(List *options, options_spec_set * spec_set);
+
+
+#endif   /* OPTIONS_H */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index f740513303..8514399e11 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -1,14 +1,9 @@
 /*-------------------------------------------------------------------------
  *
  * reloptions.h
- *	  Core support for relation and tablespace options (pg_class.reloptions
+ *	  Support for relation view and tablespace options (pg_class.reloptions
  *	  and pg_tablespace.spcoptions)
  *
- * Note: the functions dealing with text-array reloptions values declare
- * them as Datum, not ArrayType *, to avoid needing to include array.h
- * into a lot of low-level code.
- *
- *
  * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
@@ -22,178 +17,39 @@
 #include "access/amapi.h"
 #include "access/htup.h"
 #include "access/tupdesc.h"
+#include "access/options.h"
 #include "nodes/pg_list.h"
 #include "storage/lock.h"
 
-/* types supported by reloptions */
-typedef enum relopt_type
-{
-	RELOPT_TYPE_BOOL,
-	RELOPT_TYPE_INT,
-	RELOPT_TYPE_REAL,
-	RELOPT_TYPE_ENUM,
-	RELOPT_TYPE_STRING
-} relopt_type;
-
-/* kinds supported by reloptions */
-typedef enum relopt_kind
-{
-	RELOPT_KIND_LOCAL = 0,
-	RELOPT_KIND_HEAP = (1 << 0),
-	RELOPT_KIND_TOAST = (1 << 1),
-	RELOPT_KIND_BTREE = (1 << 2),
-	RELOPT_KIND_HASH = (1 << 3),
-	RELOPT_KIND_GIN = (1 << 4),
-	RELOPT_KIND_GIST = (1 << 5),
-	RELOPT_KIND_ATTRIBUTE = (1 << 6),
-	RELOPT_KIND_TABLESPACE = (1 << 7),
-	RELOPT_KIND_SPGIST = (1 << 8),
-	RELOPT_KIND_VIEW = (1 << 9),
-	RELOPT_KIND_BRIN = (1 << 10),
-	RELOPT_KIND_PARTITIONED = (1 << 11),
-	/* if you add a new kind, make sure you update "last_default" too */
-	RELOPT_KIND_LAST_DEFAULT = RELOPT_KIND_PARTITIONED,
-	/* some compilers treat enums as signed ints, so we can't use 1 << 31 */
-	RELOPT_KIND_MAX = (1 << 30)
-} relopt_kind;
-
 /* reloption namespaces allowed for heaps -- currently only TOAST */
 #define HEAP_RELOPT_NAMESPACES { "toast", NULL }
 
-/* generic struct to hold shared data */
-typedef struct relopt_gen
-{
-	const char *name;			/* must be first (used as list termination
-								 * marker) */
-	const char *desc;
-	bits32		kinds;
-	LOCKMODE	lockmode;
-	int			namelen;
-	relopt_type type;
-} relopt_gen;
-
-/* holds a parsed value */
-typedef struct relopt_value
-{
-	relopt_gen *gen;
-	bool		isset;
-	union
-	{
-		bool		bool_val;
-		int			int_val;
-		double		real_val;
-		int			enum_val;
-		char	   *string_val; /* allocated separately */
-	}			values;
-} relopt_value;
-
-/* reloptions records for specific variable types */
-typedef struct relopt_bool
-{
-	relopt_gen	gen;
-	bool		default_val;
-} relopt_bool;
-
-typedef struct relopt_int
-{
-	relopt_gen	gen;
-	int			default_val;
-	int			min;
-	int			max;
-} relopt_int;
-
-typedef struct relopt_real
-{
-	relopt_gen	gen;
-	double		default_val;
-	double		min;
-	double		max;
-} relopt_real;
-
 /*
- * relopt_enum_elt_def -- One member of the array of acceptable values
- * of an enum reloption.
+ * backward compatibility aliases so local reloption code of custom validator
+ * can work.
  */
-typedef struct relopt_enum_elt_def
-{
-	const char *string_val;
-	int			symbol_val;
-} relopt_enum_elt_def;
+typedef option_value relopt_value;
+typedef fill_string_option fill_string_relopt;
+typedef validate_string_option validate_string_relopt;
+#define GET_STRING_RELOPTION(optstruct, member) \
+			GET_STRING_OPTION(optstruct, member)
 
-typedef struct relopt_enum
-{
-	relopt_gen	gen;
-	relopt_enum_elt_def *members;
-	int			default_val;
-	const char *detailmsg;
-	/* null-terminated array of members */
-} relopt_enum;
 
-/* validation routines for strings */
-typedef void (*validate_string_relopt) (const char *value);
-typedef Size (*fill_string_relopt) (const char *value, void *ptr);
+/*
+ * relopts_validator functions is left for backward compatibility for using
+ * with local reloptions. Should not be used elsewhere
+ */
 
 /* validation routine for the whole option set */
-typedef void (*relopts_validator) (void *parsed_options, relopt_value *vals, int nvals);
-
-typedef struct relopt_string
-{
-	relopt_gen	gen;
-	int			default_len;
-	bool		default_isnull;
-	validate_string_relopt validate_cb;
-	fill_string_relopt fill_cb;
-	char	   *default_val;
-} relopt_string;
-
-/* This is the table datatype for build_reloptions() */
-typedef struct
-{
-	const char *optname;		/* option's name */
-	relopt_type opttype;		/* option's datatype */
-	int			offset;			/* offset of field in result struct */
-} relopt_parse_elt;
-
-/* Local reloption definition */
-typedef struct local_relopt
-{
-	relopt_gen *option;			/* option definition */
-	int			offset;			/* offset of parsed value in bytea structure */
-} local_relopt;
-
+typedef void (*relopts_validator) (void *parsed_options, relopt_value *vals,
+																	int nvals);
 /* Structure to hold local reloption data for build_local_reloptions() */
 typedef struct local_relopts
 {
-	List	   *options;		/* list of local_relopt definitions */
-	List	   *validators;		/* list of relopts_validator callbacks */
-	Size		relopt_struct_size; /* size of parsed bytea structure */
+	List			 *validators;	/* list of relopts_validator callbacks */
+	options_spec_set *spec_set;		/* Spec Set to store options info */
 } local_relopts;
 
-/*
- * Utility macro to get a value for a string reloption once the options
- * are parsed.  This gets a pointer to the string value itself.  "optstruct"
- * is the StdRdOptions struct or equivalent, "member" is the struct member
- * corresponding to the string option.
- */
-#define GET_STRING_RELOPTION(optstruct, member) \
-	((optstruct)->member == 0 ? NULL : \
-	 (char *)(optstruct) + (optstruct)->member)
-
-extern relopt_kind add_reloption_kind(void);
-extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
-							   bool default_val, LOCKMODE lockmode);
-extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
-							  int default_val, int min_val, int max_val,
-							  LOCKMODE lockmode);
-extern void add_real_reloption(bits32 kinds, const char *name, const char *desc,
-							   double default_val, double min_val, double max_val,
-							   LOCKMODE lockmode);
-extern void add_enum_reloption(bits32 kinds, const char *name, const char *desc,
-							   relopt_enum_elt_def *members, int default_val,
-							   const char *detailmsg, LOCKMODE lockmode);
-extern void add_string_reloption(bits32 kinds, const char *name, const char *desc,
-								 const char *default_val, validate_string_relopt validator,
-								 LOCKMODE lockmode);
 
 extern void init_local_reloptions(local_relopts *opts, Size relopt_struct_size);
 extern void register_reloptions_validator(local_relopts *opts,
@@ -210,7 +66,7 @@ extern void add_local_real_reloption(local_relopts *opts, const char *name,
 									 int offset);
 extern void add_local_enum_reloption(local_relopts *relopts,
 									 const char *name, const char *desc,
-									 relopt_enum_elt_def *members,
+									 opt_enum_elt_def *members,
 									 int default_val, const char *detailmsg,
 									 int offset);
 extern void add_local_string_reloption(local_relopts *opts, const char *name,
@@ -219,29 +75,17 @@ extern void add_local_string_reloption(local_relopts *opts, const char *name,
 									   validate_string_relopt validator,
 									   fill_string_relopt filler, int offset);
 
-extern Datum transformRelOptions(Datum oldOptions, List *defList,
-								 const char *namspace, char *validnsps[],
-								 bool acceptOidsOff, bool isReset);
-extern List *untransformRelOptions(Datum options);
 extern bytea *extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
-								amoptions_function amoptions);
-extern void *build_reloptions(Datum reloptions, bool validate,
-							  relopt_kind kind,
-							  Size relopt_struct_size,
-							  const relopt_parse_elt *relopt_elems,
-							  int num_relopt_elems);
+								amreloptspecset_function amoptions_def_set);
 extern void *build_local_reloptions(local_relopts *relopts, Datum options,
 									bool validate);
 
-extern bytea *default_reloptions(Datum reloptions, bool validate,
-								 relopt_kind kind);
-extern bytea *heap_reloptions(char relkind, Datum reloptions, bool validate);
-extern bytea *view_reloptions(Datum reloptions, bool validate);
-extern bytea *partitioned_table_reloptions(Datum reloptions, bool validate);
-extern bytea *index_reloptions(amoptions_function amoptions, Datum reloptions,
-							   bool validate);
-extern bytea *attribute_reloptions(Datum reloptions, bool validate);
-extern bytea *tablespace_reloptions(Datum reloptions, bool validate);
-extern LOCKMODE AlterTableGetRelOptionsLockLevel(List *defList);
+options_spec_set *get_heap_relopt_spec_set(void);
+options_spec_set *get_toast_relopt_spec_set(void);
+options_spec_set *get_partitioned_relopt_spec_set(void);
+options_spec_set *get_view_relopt_spec_set(void);
+options_spec_set *get_attribute_options_spec_set(void);
+options_spec_set *get_tablespace_options_spec_set(void);
+extern LOCKMODE AlterTableGetRelOptionsLockLevel(Relation rel, List *defList);
 
 #endif							/* RELOPTIONS_H */
diff --git a/src/include/access/spgist.h b/src/include/access/spgist.h
index 5ad4f85940..99664a4114 100644
--- a/src/include/access/spgist.h
+++ b/src/include/access/spgist.h
@@ -189,9 +189,6 @@ typedef struct spgLeafConsistentOut
 } spgLeafConsistentOut;
 
 
-/* spgutils.c */
-extern bytea *spgoptions(Datum reloptions, bool validate);
-
 /* spginsert.c */
 extern IndexBuildResult *spgbuild(Relation heap, Relation index,
 								  struct IndexInfo *indexInfo);
diff --git a/src/include/access/spgist_private.h b/src/include/access/spgist_private.h
index eb56b1c6b8..d3ffe645c3 100644
--- a/src/include/access/spgist_private.h
+++ b/src/include/access/spgist_private.h
@@ -529,6 +529,7 @@ extern OffsetNumber SpGistPageAddNewItem(SpGistState *state, Page page,
 extern bool spgproperty(Oid index_oid, int attno,
 						IndexAMProperty prop, const char *propname,
 						bool *res, bool *isnull);
+extern void *spggetreloptspecset(void);
 
 /* spgdoinsert.c */
 extern void spgUpdateNodeLink(SpGistInnerTuple tup, int nodeN,
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 5d4037f26e..1086723b00 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -34,7 +34,7 @@ extern Oid	AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode);
 extern void AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 					   struct AlterTableUtilityContext *context);
 
-extern LOCKMODE AlterTableGetLockLevel(List *cmds);
+extern LOCKMODE AlterTableGetLockLevel(Oid relid, List *cmds);
 
 extern void ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lockmode);
 
diff --git a/src/test/modules/dummy_index_am/dummy_index_am.c b/src/test/modules/dummy_index_am/dummy_index_am.c
index 22578b6246..bfb0da3c4a 100644
--- a/src/test/modules/dummy_index_am/dummy_index_am.c
+++ b/src/test/modules/dummy_index_am/dummy_index_am.c
@@ -14,7 +14,7 @@
 #include "postgres.h"
 
 #include "access/amapi.h"
-#include "access/reloptions.h"
+#include "access/options.h"
 #include "catalog/index.h"
 #include "commands/vacuum.h"
 #include "nodes/pathnodes.h"
@@ -25,12 +25,6 @@ PG_MODULE_MAGIC;
 
 void		_PG_init(void);
 
-/* parse table for fillRelOptions */
-relopt_parse_elt di_relopt_tab[6];
-
-/* Kind of relation options for dummy index */
-relopt_kind di_relopt_kind;
-
 typedef enum DummyAmEnum
 {
 	DUMMY_AM_ENUM_ONE,
@@ -49,7 +43,7 @@ typedef struct DummyIndexOptions
 	int			option_string_null_offset;
 }			DummyIndexOptions;
 
-relopt_enum_elt_def dummyAmEnumValues[] =
+opt_enum_elt_def dummyAmEnumValues[] =
 {
 	{"one", DUMMY_AM_ENUM_ONE},
 	{"two", DUMMY_AM_ENUM_TWO},
@@ -63,77 +57,82 @@ PG_FUNCTION_INFO_V1(dihandler);
  * Validation function for string relation options.
  */
 static void
-validate_string_option(const char *value)
+divalidate_string_option(const char *value)
 {
 	ereport(NOTICE,
 			(errmsg("new option value for string parameter %s",
 					value ? value : "NULL")));
 }
 
-/*
- * This function creates a full set of relation option types,
- * with various patterns.
- */
-static void
-create_reloptions_table(void)
+static options_spec_set *di_relopt_specset = NULL;
+void * digetreloptspecset(void);
+
+void *
+digetreloptspecset(void)
 {
-	di_relopt_kind = add_reloption_kind();
-
-	add_int_reloption(di_relopt_kind, "option_int",
-					  "Integer option for dummy_index_am",
-					  10, -10, 100, AccessExclusiveLock);
-	di_relopt_tab[0].optname = "option_int";
-	di_relopt_tab[0].opttype = RELOPT_TYPE_INT;
-	di_relopt_tab[0].offset = offsetof(DummyIndexOptions, option_int);
-
-	add_real_reloption(di_relopt_kind, "option_real",
-					   "Real option for dummy_index_am",
-					   3.1415, -10, 100, AccessExclusiveLock);
-	di_relopt_tab[1].optname = "option_real";
-	di_relopt_tab[1].opttype = RELOPT_TYPE_REAL;
-	di_relopt_tab[1].offset = offsetof(DummyIndexOptions, option_real);
-
-	add_bool_reloption(di_relopt_kind, "option_bool",
-					   "Boolean option for dummy_index_am",
-					   true, AccessExclusiveLock);
-	di_relopt_tab[2].optname = "option_bool";
-	di_relopt_tab[2].opttype = RELOPT_TYPE_BOOL;
-	di_relopt_tab[2].offset = offsetof(DummyIndexOptions, option_bool);
-
-	add_enum_reloption(di_relopt_kind, "option_enum",
-					   "Enum option for dummy_index_am",
-					   dummyAmEnumValues,
-					   DUMMY_AM_ENUM_ONE,
-					   "Valid values are \"one\" and \"two\".",
-					   AccessExclusiveLock);
-	di_relopt_tab[3].optname = "option_enum";
-	di_relopt_tab[3].opttype = RELOPT_TYPE_ENUM;
-	di_relopt_tab[3].offset = offsetof(DummyIndexOptions, option_enum);
-
-	add_string_reloption(di_relopt_kind, "option_string_val",
-						 "String option for dummy_index_am with non-NULL default",
-						 "DefaultValue", &validate_string_option,
-						 AccessExclusiveLock);
-	di_relopt_tab[4].optname = "option_string_val";
-	di_relopt_tab[4].opttype = RELOPT_TYPE_STRING;
-	di_relopt_tab[4].offset = offsetof(DummyIndexOptions,
-									   option_string_val_offset);
+	if (di_relopt_specset)
+		return di_relopt_specset;
+
+	di_relopt_specset = allocateOptionsSpecSet(NULL,
+										   sizeof(DummyIndexOptions), false, 6);
+
+	optionsSpecSetAddInt(
+		di_relopt_specset, "option_int",
+		"Integer option for dummy_index_am",
+		AccessExclusiveLock,
+		offsetof(DummyIndexOptions, option_int), NULL,
+		10, -10, 100
+	);
+
+
+	optionsSpecSetAddReal(
+		di_relopt_specset, "option_real",
+		"Real option for dummy_index_am",
+		AccessExclusiveLock,
+		offsetof(DummyIndexOptions, option_real), NULL,
+		3.1415, -10, 100
+	);
+
+	optionsSpecSetAddBool(
+		di_relopt_specset, "option_bool",
+		"Boolean option for dummy_index_am",
+		AccessExclusiveLock,
+		offsetof(DummyIndexOptions, option_bool), NULL, true
+	);
+
+	optionsSpecSetAddEnum(di_relopt_specset, "option_enum",
+		"Enum option for dummy_index_am",
+		AccessExclusiveLock,
+		offsetof(DummyIndexOptions, option_enum), NULL,
+		dummyAmEnumValues,
+		DUMMY_AM_ENUM_ONE,
+		"Valid values are \"one\" and \"two\"."
+	);
+
+	optionsSpecSetAddString(di_relopt_specset, "option_string_val",
+		"String option for dummy_index_am with non-NULL default",
+		AccessExclusiveLock,
+		offsetof(DummyIndexOptions, option_string_val_offset), NULL,
+		"DefaultValue", &divalidate_string_option, NULL
+	);
 
 	/*
 	 * String option for dummy_index_am with NULL default, and without
 	 * description.
 	 */
-	add_string_reloption(di_relopt_kind, "option_string_null",
-						 NULL,	/* description */
-						 NULL, &validate_string_option,
-						 AccessExclusiveLock);
-	di_relopt_tab[5].optname = "option_string_null";
-	di_relopt_tab[5].opttype = RELOPT_TYPE_STRING;
-	di_relopt_tab[5].offset = offsetof(DummyIndexOptions,
-									   option_string_null_offset);
+
+	optionsSpecSetAddString(di_relopt_specset, "option_string_null",
+		NULL,	/* description */
+		AccessExclusiveLock,
+		offsetof(DummyIndexOptions, option_string_null_offset), NULL,
+		NULL, &divalidate_string_option, NULL
+	);
+
+	return di_relopt_specset;
 }
 
 
+
 /*
  * Build a new index.
  */
@@ -218,19 +217,6 @@ dicostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	*indexPages = 1;
 }
 
-/*
- * Parse relation options for index AM, returning a DummyIndexOptions
- * structure filled with option values.
- */
-static bytea *
-dioptions(Datum reloptions, bool validate)
-{
-	return (bytea *) build_reloptions(reloptions, validate,
-									  di_relopt_kind,
-									  sizeof(DummyIndexOptions),
-									  di_relopt_tab, lengthof(di_relopt_tab));
-}
-
 /*
  * Validator for index AM.
  */
@@ -309,7 +295,6 @@ dihandler(PG_FUNCTION_ARGS)
 	amroutine->amvacuumcleanup = divacuumcleanup;
 	amroutine->amcanreturn = NULL;
 	amroutine->amcostestimate = dicostestimate;
-	amroutine->amoptions = dioptions;
 	amroutine->amproperty = NULL;
 	amroutine->ambuildphasename = NULL;
 	amroutine->amvalidate = divalidate;
@@ -323,12 +308,7 @@ dihandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->amreloptspecset = digetreloptspecset;
 
 	PG_RETURN_POINTER(amroutine);
 }
-
-void
-_PG_init(void)
-{
-	create_reloptions_table();
-}
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index b6aef6f654..72fee4176b 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -164,7 +164,7 @@ SELECT reloptions FROM pg_class WHERE oid = :toast_oid;
 
 -- Fail on non-existent options in toast namespace
 CREATE TABLE reloptions_test2 (i int) WITH (toast.not_existing_option = 42);
-ERROR:  unrecognized parameter "not_existing_option"
+ERROR:  unrecognized parameter "toast.not_existing_option"
 -- Mix TOAST & heap
 DROP TABLE reloptions_test;
 CREATE TABLE reloptions_test (s VARCHAR) WITH
@@ -183,6 +183,17 @@ SELECT reloptions FROM pg_class WHERE oid = (
  {autovacuum_vacuum_cost_delay=23}
 (1 row)
 
+-- Can reset option that is not allowed, but for some reason is already set
+UPDATE pg_class
+	SET reloptions = '{fillfactor=13,autovacuum_enabled=false,illegal_option=4}'
+	WHERE oid = 'reloptions_test'::regclass;
+ALTER TABLE reloptions_test RESET (illegal_option);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+                reloptions                
+------------------------------------------
+ {fillfactor=13,autovacuum_enabled=false}
+(1 row)
+
 --
 -- CREATE INDEX, ALTER INDEX for btrees
 --
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 4252b0202f..fadce3384d 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -105,6 +105,13 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
 SELECT reloptions FROM pg_class WHERE oid = (
 	SELECT reltoastrelid FROM pg_class WHERE oid = 'reloptions_test'::regclass);
 
+-- Can reset option that is not allowed, but for some reason is already set
+UPDATE pg_class
+	SET reloptions = '{fillfactor=13,autovacuum_enabled=false,illegal_option=4}'
+	WHERE oid = 'reloptions_test'::regclass;
+ALTER TABLE reloptions_test RESET (illegal_option);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
 --
 -- CREATE INDEX, ALTER INDEX for btrees
 --
