From 782e7d98320c6e7d9bf5c0f9aa33fa1c6047101f Mon Sep 17 00:00:00 2001
From: B Sadhu Prasad Patro <b.sadhuprasadp@enterprisedb.com>
Date: Mon, 21 Mar 2022 20:23:22 -0700
Subject: [PATCH v4] [PATCH V4] Per-table storage parameters for TableAM
 extensions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Currently all the storage options for a table are very much specific
to the heap but a different AM might need some user defined AM
specific parameters to help tune the AM. So here is a patch which
provides an AM level routine so that instead of getting parameters
validated using “heap_reloptions” it will call the registered AM
routine.

I have added new test module, which shows way to register a new table
access method.

New Command supported as:
ALTER TABLE name SET ACCESS METHOD amname [ OPTIONS ( ADD | DROP option 'value' [, ... ] ) ];
---
 doc/src/sgml/ref/create_table.sgml       |   3 +-
 src/backend/access/common/reloptions.c   |  30 ++++-
 src/backend/access/heap/heapam_handler.c |   1 +
 src/backend/commands/tablecmds.c         | 214 ++++++++++++++++++++++++++++---
 src/backend/parser/gram.y                |  43 ++++++-
 src/backend/postmaster/autovacuum.c      |  18 ++-
 src/backend/utils/cache/relcache.c       |  11 +-
 src/include/access/reloptions.h          |   6 +-
 src/include/access/tableam.h             |   8 +-
 9 files changed, 298 insertions(+), 36 deletions(-)

diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 7e4ef31..615bcad 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1375,7 +1375,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     Storage parameters for
     indexes are documented in <xref linkend="sql-createindex"/>.
     The storage parameters currently
-    available for tables are listed below.  For many of these parameters, as
+    available for tables are listed below. Each table may have different set of storage
+    parameters through different access methods. For many of these parameters, as
     shown, there is an additional parameter with the same name prefixed with
     <literal>toast.</literal>, which controls the behavior of the
     table's secondary <acronym>TOAST</acronym> table, if any
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index d592655..bcb08d7 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -1372,7 +1372,8 @@ untransformRelOptions(Datum options)
  */
 bytea *
 extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
-				  amoptions_function amoptions)
+				 amoptions_function amoptions,
+				 reloptions_function reloptions)
 {
 	bytea	   *options;
 	bool		isnull;
@@ -1394,7 +1395,9 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
-			options = heap_reloptions(classForm->relkind, datum, false);
+			options = table_reloptions(reloptions,
+							classForm->relkind,
+							datum, false);
 			break;
 		case RELKIND_PARTITIONED_TABLE:
 			options = partitioned_table_reloptions(datum, false);
@@ -2007,7 +2010,8 @@ view_reloptions(Datum reloptions, bool validate)
 }
 
 /*
- * Parse options for heaps, views and toast tables.
+ * Parse options for heaps, views and toast tables. This is
+ * implementation of relOptions for access method heap.
  */
 bytea *
 heap_reloptions(char relkind, Datum reloptions, bool validate)
@@ -2038,6 +2042,26 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 
 
 /*
+ * Parse options for tables.
+ *
+ *	reloptions	tables AM's option parser function
+ *	reloptions	options as text[] datum
+ *	validate	error flag
+ */
+bytea *
+table_reloptions(reloptions_function reloptsfun, char relkind,
+				 Datum reloptions, bool validate)
+{
+	Assert(reloptsfun != NULL);
+
+	/* Assume function is strict */
+	if (!PointerIsValid(DatumGetPointer(reloptions)))
+		return NULL;
+
+	return reloptsfun(relkind, reloptions, validate);
+}
+
+/*
  * Parse options for indexes.
  *
  *	amoptions	index AM's option parser function
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 39ef8a0..4f7f110 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -2581,6 +2581,7 @@ static const TableAmRoutine heapam_methods = {
 	.index_build_range_scan = heapam_index_build_range_scan,
 	.index_validate_scan = heapam_index_validate_scan,
 
+	.relation_options = heap_reloptions,
 	.relation_size = table_block_relation_size,
 	.relation_needs_toast_table = heapam_relation_needs_toast_table,
 	.relation_toast_am = heapam_relation_toast_am,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 80faae9..e2d5172 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -574,6 +574,7 @@ static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
 								const char *tablespacename, LOCKMODE lockmode);
 static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
 static void ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace);
+static void ATExecSetAMOptions(Relation rel, List *defList, AlterTableType operation, LOCKMODE lockmode);
 static void ATExecSetRelOptions(Relation rel, List *defList,
 								AlterTableType operation,
 								LOCKMODE lockmode);
@@ -816,24 +817,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	if (!OidIsValid(ownerId))
 		ownerId = GetUserId();
 
-	/*
-	 * 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);
-			break;
-		case RELKIND_PARTITIONED_TABLE:
-			(void) partitioned_table_reloptions(reloptions, true);
-			break;
-		default:
-			(void) heap_reloptions(relkind, reloptions, true);
-	}
-
 	if (stmt->ofTypename)
 	{
 		AclResult	aclresult;
@@ -954,6 +937,52 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		accessMethodId = get_table_am_oid(accessMethod, false);
 
 	/*
+	 * 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);
+			break;
+		case RELKIND_PARTITIONED_TABLE:
+			(void) partitioned_table_reloptions(reloptions, true);
+			break;
+		case RELKIND_RELATION:
+		case RELKIND_TOASTVALUE:
+		case RELKIND_MATVIEW:
+			{
+				const TableAmRoutine *routine;
+				HeapTuple	tuple;
+				Form_pg_am	aform;
+
+				tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(accessMethodId));
+				if (!HeapTupleIsValid(tuple))
+				{
+					elog(ERROR, "cache lookup failed for access method %u",
+						 accessMethodId);
+				}
+
+				aform = (Form_pg_am) GETSTRUCT(tuple);
+				routine = GetTableAmRoutine(aform->amhandler);
+				if (routine->relation_options == NULL)
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("specifying a table access method is not supported")));
+				}
+
+				(void) routine->relation_options(relkind, reloptions, true);
+				ReleaseSysCache(tuple);
+				break;
+			}
+
+		default:
+			(void) heap_reloptions(relkind, reloptions, true);
+	}
+
+	/*
 	 * Create the relation.  Inherited defaults and constraints are passed in
 	 * for immediate handling --- since they don't need parsing, they can be
 	 * stored immediately.
@@ -5087,6 +5116,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
 			break;
 		case AT_SetAccessMethod:	/* SET ACCESS METHOD */
 			/* handled specially in Phase 3 */
+			ATExecSetAMOptions(rel, (List *) cmd->def, cmd->subtype, lockmode);
 			break;
 		case AT_SetTableSpace:	/* SET TABLESPACE */
 
@@ -14103,6 +14133,152 @@ ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, const char *tablespacen
 	tab->newTableSpace = tablespaceId;
 }
 
+/*TODO: This needs to be removed. Same function is already present in foreigncmds.c*/
+static Datum
+optionListToArray(List *options)
+{
+	ArrayBuildState *astate = NULL;
+	ListCell   *cell;
+
+	foreach(cell, options)
+	{
+		DefElem    *def = lfirst(cell);
+		const char *value;
+		Size		len;
+		text	   *t;
+
+		value = defGetString(def);
+		len = VARHDRSZ + strlen(def->defname) + 1 + strlen(value);
+		t = 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)
+		return makeArrayResult(astate, CurrentMemoryContext);
+
+	return PointerGetDatum(NULL);
+}
+
+/* ADD or DROP reloptions in SET ACCESS METHOD.*/
+static void
+ATExecSetAMOptions(Relation rel, List *defList, AlterTableType operation,
+					LOCKMODE lockmode)
+{
+	Oid			relid;
+	Relation	pgclass;
+	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];
+	List		*resultOptions;
+	ListCell	*optcell;
+
+	if (defList == NIL)
+		return; /* nothing to do */
+
+	pgclass = table_open(RelationRelationId, RowExclusiveLock);
+
+	/* Fetch heap tuple */
+	relid = RelationGetRelid(rel);
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", relid);
+
+	/* Get the old reloptions */
+	datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions,
+								&isnull);
+	resultOptions = untransformRelOptions(datum);
+
+	foreach(optcell, defList)
+	{
+		DefElem    *od = lfirst(optcell);
+		ListCell   *cell;
+
+		/* Search in existing options */
+		foreach(cell, resultOptions)
+		{
+			DefElem    *def = lfirst(cell);
+
+			if (strcmp(def->defname, od->defname) == 0)
+				break;
+		}
+
+		/*
+		 * It is possible to perform multiple ADD/DROP actions on the same
+		 * option.  The standard permits this, as long as the options to be
+		 * added are unique.
+		 */
+		switch (od->defaction)
+		{
+			case DEFELEM_ADD:
+				if (cell)
+					ereport(ERROR,
+							(errcode(ERRCODE_DUPLICATE_OBJECT),
+							 errmsg("option \"%s\" provided more than once",
+									od->defname)));
+				resultOptions = lappend(resultOptions, od);
+				break;
+
+			case DEFELEM_DROP:
+				if (!cell)
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_OBJECT),
+							 errmsg("option \"%s\" not found",
+									od->defname)));
+				resultOptions = list_delete_cell(resultOptions, cell);
+				break;
+
+			default:
+				elog(ERROR, "unrecognized action %d on option \"%s\"",
+					 (int) od->defaction, od->defname);
+				break;
+		}
+	}
+
+	newOptions = optionListToArray(resultOptions);
+
+	/* DO need to call the handler function to validate ?*/
+
+	/*
+	 * All we need do here is update the pg_class row; the new options will be
+	 * propagated into relcaches during post-commit cache inval.
+	 */
+	memset(repl_val, 0, sizeof(repl_val));
+	memset(repl_null, false, sizeof(repl_null));
+	memset(repl_repl, false, sizeof(repl_repl));
+
+	if (newOptions != (Datum) 0)
+		repl_val[Anum_pg_class_reloptions - 1] = newOptions;
+	else
+		repl_null[Anum_pg_class_reloptions - 1] = true;
+
+	repl_repl[Anum_pg_class_reloptions - 1] = true;
+
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(pgclass),
+								 repl_val, repl_null, repl_repl);
+
+	CatalogTupleUpdate(pgclass, &newtuple->t_self, newtuple);
+
+
+	InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel),
+								  InvalidOid);
+
+	heap_freetuple(newtuple);
+
+	ReleaseSysCache(tuple);
+
+	table_close(pgclass, RowExclusiveLock);
+}
+
 /*
  * Set, reset, or replace reloptions.
  */
@@ -14160,7 +14336,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
-			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
+			rel->rd_tableam->relation_options(rel->rd_rel->relkind, newOptions, true);
 			break;
 		case RELKIND_PARTITIONED_TABLE:
 			(void) partitioned_table_reloptions(newOptions, true);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0036c2f..2b1d870 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -441,7 +441,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				prep_type_clause
 				execute_param_clause using_clause returning_clause
 				opt_enum_val_list enum_val_list table_func_column_list
-				create_generic_options alter_generic_options
+				create_generic_options alter_generic_options alter_am_options
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
 				TriggerTransitions TriggerReferencing
@@ -544,8 +544,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <str>		generic_option_name
 %type <node>	generic_option_arg
-%type <defelt>	generic_option_elem alter_generic_option_elem
-%type <list>	generic_option_list alter_generic_option_list
+%type <defelt>	generic_option_elem alter_generic_option_elem alter_am_option_elem
+%type <list>	generic_option_list alter_generic_option_list alter_am_option_list
 
 %type <ival>	reindex_target_type reindex_target_multitable
 
@@ -2655,6 +2655,14 @@ alter_table_cmd:
 					n->name = $4;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> SET ACCESS METHOD <amname> [OPTIONS]*/
+			| SET ACCESS METHOD name alter_am_options
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetAccessMethod;
+					n->name = $4;
+					$$ = (Node *)n;
+				}
 			/* ALTER TABLE <name> SET TABLESPACE <tablespacename> */
 			| SET TABLESPACE name
 				{
@@ -2724,6 +2732,35 @@ alter_table_cmd:
 				}
 		;
 
+/* Options definition for ALTER TABLE <name> SET ACCESS METHOD <amname> OPTIONS (...) */
+alter_am_options:
+			OPTIONS '(' alter_am_option_list ')'               { $$ = $3; }
+		;
+
+alter_am_option_list:
+			alter_am_option_elem
+				{
+					$$ = list_make1($1);
+				}
+			| alter_am_option_list ',' alter_am_option_elem
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
+alter_am_option_elem:
+			ADD_P generic_option_elem
+				{
+					$$ = $2;
+					$$->defaction = DEFELEM_ADD;
+				}
+			| DROP generic_option_elem
+				{
+					$$ = makeDefElemExtended(NULL, $2, NULL, DEFELEM_DROP, @2);
+				}
+		;
+
+
 alter_column_default:
 			SET DEFAULT a_expr			{ $$ = $3; }
 			| DROP DEFAULT				{ $$ = NULL; }
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 681ef91..cd9c0bb 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -327,6 +327,7 @@ static void FreeWorkerInfo(int code, Datum arg);
 
 static autovac_table *table_recheck_autovac(Oid relid, HTAB *table_toast_map,
 											TupleDesc pg_class_desc,
+											reloptions_function reloptions,
 											int effective_multixact_freeze_max_age);
 static void recheck_relation_needs_vacanalyze(Oid relid, AutoVacOpts *avopts,
 											  Form_pg_class classForm,
@@ -341,7 +342,7 @@ static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts,
 static void autovacuum_do_vac_analyze(autovac_table *tab,
 									  BufferAccessStrategy bstrategy);
 static AutoVacOpts *extract_autovac_opts(HeapTuple tup,
-										 TupleDesc pg_class_desc);
+										 TupleDesc pg_class_desc, reloptions_function reloptions);
 static PgStat_StatTabEntry *get_pgstat_tabentry_relid(Oid relid, bool isshared,
 													  PgStat_StatDBEntry *shared,
 													  PgStat_StatDBEntry *dbentry);
@@ -2118,7 +2119,8 @@ do_autovacuum(void)
 		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
-		relopts = extract_autovac_opts(tuple, pg_class_desc);
+		relopts = extract_autovac_opts(tuple, pg_class_desc,
+									   classRel->rd_tableam->relation_options);
 		tabentry = get_pgstat_tabentry_relid(relid, classForm->relisshared,
 											 shared, dbentry);
 
@@ -2191,7 +2193,8 @@ do_autovacuum(void)
 		 * fetch reloptions -- if this toast table does not have them, try the
 		 * main rel
 		 */
-		relopts = extract_autovac_opts(tuple, pg_class_desc);
+		relopts = extract_autovac_opts(tuple, pg_class_desc,
+									   classRel->rd_tableam->relation_options);
 		if (relopts == NULL)
 		{
 			av_relation *hentry;
@@ -2427,6 +2430,7 @@ do_autovacuum(void)
 		 */
 		MemoryContextSwitchTo(AutovacMemCxt);
 		tab = table_recheck_autovac(relid, table_toast_map, pg_class_desc,
+									classRel->rd_tableam->relation_options,
 									effective_multixact_freeze_max_age);
 		if (tab == NULL)
 		{
@@ -2748,7 +2752,8 @@ deleted2:
  * be a risk; fortunately, it doesn't.
  */
 static AutoVacOpts *
-extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc)
+extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc,
+					 reloptions_function reloptions)
 {
 	bytea	   *relopts;
 	AutoVacOpts *av;
@@ -2757,7 +2762,7 @@ extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc)
 		   ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_MATVIEW ||
 		   ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_TOASTVALUE);
 
-	relopts = extractRelOptions(tup, pg_class_desc, NULL);
+	relopts = extractRelOptions(tup, pg_class_desc, NULL, reloptions);
 	if (relopts == NULL)
 		return NULL;
 
@@ -2803,6 +2808,7 @@ get_pgstat_tabentry_relid(Oid relid, bool isshared, PgStat_StatDBEntry *shared,
 static autovac_table *
 table_recheck_autovac(Oid relid, HTAB *table_toast_map,
 					  TupleDesc pg_class_desc,
+					  reloptions_function reloptions,
 					  int effective_multixact_freeze_max_age)
 {
 	Form_pg_class classForm;
@@ -2824,7 +2830,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
 	 * Get the applicable reloptions.  If it is a TOAST table, try to get the
 	 * main table reloptions if the toast table itself doesn't have.
 	 */
-	avopts = extract_autovac_opts(classTup, pg_class_desc);
+	avopts = extract_autovac_opts(classTup, pg_class_desc, reloptions);
 	if (classForm->relkind == RELKIND_TOASTVALUE &&
 		avopts == NULL && table_toast_map != NULL)
 	{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index fccffce..91ec1b7 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -461,6 +461,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 {
 	bytea	   *options;
 	amoptions_function amoptsfn;
+	reloptions_function reloptsfn;
 
 	relation->rd_options = NULL;
 
@@ -472,13 +473,18 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 	{
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
-		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
+			reloptsfn = relation->rd_tableam->relation_options;
+			amoptsfn = NULL;
+			break;
+		case RELKIND_VIEW:
 		case RELKIND_PARTITIONED_TABLE:
+			reloptsfn = NULL;
 			amoptsfn = NULL;
 			break;
 		case RELKIND_INDEX:
 		case RELKIND_PARTITIONED_INDEX:
+			reloptsfn = NULL;
 			amoptsfn = relation->rd_indam->amoptions;
 			break;
 		default:
@@ -490,7 +496,8 @@ 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(),
+								amoptsfn, reloptsfn);
 
 	/*
 	 * Copy parsed data into CacheMemoryContext.  To guard against the
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index f740513..c42d5e9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -21,6 +21,7 @@
 
 #include "access/amapi.h"
 #include "access/htup.h"
+#include "access/tableam.h"
 #include "access/tupdesc.h"
 #include "nodes/pg_list.h"
 #include "storage/lock.h"
@@ -224,7 +225,8 @@ extern Datum transformRelOptions(Datum oldOptions, List *defList,
 								 bool acceptOidsOff, bool isReset);
 extern List *untransformRelOptions(Datum options);
 extern bytea *extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
-								amoptions_function amoptions);
+								amoptions_function amoptions,
+								reloptions_function reloptions);
 extern void *build_reloptions(Datum reloptions, bool validate,
 							  relopt_kind kind,
 							  Size relopt_struct_size,
@@ -238,6 +240,8 @@ extern bytea *default_reloptions(Datum reloptions, bool validate,
 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 *table_reloptions(reloptions_function reloptsfun, char relkind,
+							   Datum reloptions, bool validate);
 extern bytea *index_reloptions(amoptions_function amoptions, Datum reloptions,
 							   bool validate);
 extern bytea *attribute_reloptions(Datum reloptions, bool validate);
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index bb36573..e110085 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -252,6 +252,10 @@ typedef void (*IndexBuildCallback) (Relation index,
 									bool tupleIsAlive,
 									void *state);
 
+/* This callback parse the table reloptions and returns in bytea format */
+typedef bytea *(*reloptions_function) (char relkind,
+									   Datum reloptions, bool validate);
+
 /*
  * API struct for a table AM.  Note this must be allocated in a
  * server-lifetime manner, typically as a static const struct, which then gets
@@ -692,6 +696,8 @@ typedef struct TableAmRoutine
 	 * ------------------------------------------------------------------------
 	 */
 
+	reloptions_function relation_options;
+
 	/*
 	 * See table_relation_size().
 	 *
@@ -702,7 +708,6 @@ typedef struct TableAmRoutine
 	 */
 	uint64		(*relation_size) (Relation rel, ForkNumber forkNumber);
 
-
 	/*
 	 * This callback should return true if the relation requires a TOAST table
 	 * and false if it does not.  It may wish to examine the relation's tuple
@@ -2073,5 +2078,6 @@ extern const TableAmRoutine *GetTableAmRoutine(Oid amhandler);
 extern const TableAmRoutine *GetHeapamTableAmRoutine(void);
 extern bool check_default_table_access_method(char **newval, void **extra,
 											  GucSource source);
+extern bytea *heap_reloptions(char relkind, Datum reloptions, bool validate);
 
 #endif							/* TABLEAM_H */
-- 
1.8.3.1

