Thread

  1. [PATCH 1/2] Allow table AMs to define their own reloptions

    Julien Tachoires <julien@tachoires.me> — 2025-03-01T16:59:49Z

    With the help of the new routine 'relation_options', table access
    methods can now define their own reloptions.
    
    These options can be set via the following commands:
    1. CREATE TABLE ... USING table_am
           WITH (option1='value1', option2='value2');
    2. ALTER TABLE ...
           SET (option1 'value1', option2 'value2');
    3. ALTER TABLE ... SET ACCESS METHOD table_am
           OPTIONS (option1 'value1', option2 'value2');
    
    When changing table's access method, the settings from the former
    table AM can be dropped (if not supported by the new table AM) via:
    DROP option, or, updated via: SET option 'value'.
    
    Before this commit, tables using different table AMs than heap were
    able to use heap's reloptions (fillfactor, toast_tuple_target,
    etc...). Now, this is not the case anymore: if the table AM needs
    to have access to settings similar to heap ones, they must
    explicitly define them.
    
    This work is directly derived from SadhuPrasad's patch named:
    v4-0001-PATCH-V4-Per-table-storage-parameters-for-TableAM.patch
    ---
     doc/src/sgml/ref/alter_table.sgml        |  13 +-
     doc/src/sgml/ref/create_table.sgml       |   3 +-
     src/backend/access/common/reloptions.c   |  66 ++++++++-
     src/backend/access/heap/heapam_handler.c |   2 +
     src/backend/commands/foreigncmds.c       |   2 +-
     src/backend/commands/tablecmds.c         | 180 ++++++++++++++++++++---
     src/backend/parser/gram.y                |   9 ++
     src/backend/postmaster/autovacuum.c      |  18 ++-
     src/backend/utils/cache/relcache.c       |  11 +-
     src/include/access/reloptions.h          |   6 +-
     src/include/access/tableam.h             |  10 ++
     src/include/commands/defrem.h            |   1 +
     12 files changed, 286 insertions(+), 35 deletions(-)
    
    diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
    index d63f3a621ac..e567aad4774 100644
    --- a/doc/src/sgml/ref/alter_table.sgml
    +++ b/doc/src/sgml/ref/alter_table.sgml
    @@ -77,7 +77,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
         CLUSTER ON <replaceable class="parameter">index_name</replaceable>
         SET WITHOUT CLUSTER
         SET WITHOUT OIDS
    -    SET ACCESS METHOD { <replaceable class="parameter">new_access_method</replaceable> | DEFAULT }
    +    SET ACCESS METHOD { <replaceable class="parameter">new_access_method</replaceable> | DEFAULT } [ OPTIONS ( [ ADD | SET | DROP ] <replaceable class="parameter">option</replaceable> ['<replaceable class="parameter">value</replaceable>'] [, ... ] ) ]
         SET TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
         SET { LOGGED | UNLOGGED }
         SET ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
    @@ -758,7 +758,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
        </varlistentry>
     
        <varlistentry id="sql-altertable-desc-set-access-method">
    -    <term><literal>SET ACCESS METHOD</literal></term>
    +    <term><literal>SET ACCESS METHOD { <replaceable class="parameter">new_access_method</replaceable> | DEFAULT } [ OPTIONS ( [ ADD | SET | DROP ] <replaceable class="parameter">option</replaceable> ['<replaceable class="parameter">value</replaceable>'] [, ... ] ) ]</literal></term>
         <listitem>
          <para>
           This form changes the access method of the table by rewriting it
    @@ -776,6 +776,15 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
           causing future partitions to default to
           <varname>default_table_access_method</varname>.
          </para>
    +     <para>
    +      Specifying <literal>OPTIONS</literal> allows to change options for
    +      the table when changing the table access method.
    +      <literal>ADD</literal>, <literal>SET</literal>, and
    +      <literal>DROP</literal> specify the action to be performed.
    +      <literal>ADD</literal> is assumed if no operation is explicitly
    +      specified.  Option names must be unique; names and values are also
    +      validated using the table access method's library.
    +     </para>
         </listitem>
        </varlistentry>
     
    diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
    index 4a41b2f5530..ad642e55d22 100644
    --- a/doc/src/sgml/ref/create_table.sgml
    +++ b/doc/src/sgml/ref/create_table.sgml
    @@ -1552,7 +1552,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 46c1dce222d..0d8ead7ce5d 100644
    --- a/src/backend/access/common/reloptions.c
    +++ b/src/backend/access/common/reloptions.c
    @@ -25,6 +25,7 @@
     #include "access/reloptions.h"
     #include "access/spgist_private.h"
     #include "catalog/pg_type.h"
    +#include "catalog/pg_am.h"
     #include "commands/defrem.h"
     #include "commands/tablespace.h"
     #include "nodes/makefuncs.h"
    @@ -34,6 +35,7 @@
     #include "utils/guc.h"
     #include "utils/memutils.h"
     #include "utils/rel.h"
    +#include "utils/syscache.h"
     
     /*
      * Contents of pg_class.reloptions
    @@ -1388,7 +1390,7 @@ untransformRelOptions(Datum options)
      */
     bytea *
     extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
    -				  amoptions_function amoptions)
    +				  amoptions_function amoptions, reloptions_function reloptsfun)
     {
     	bytea	   *options;
     	bool		isnull;
    @@ -1410,7 +1412,8 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
     		case RELKIND_RELATION:
     		case RELKIND_TOASTVALUE:
     		case RELKIND_MATVIEW:
    -			options = heap_reloptions(classForm->relkind, datum, false);
    +			options = table_reloptions(reloptsfun, InvalidOid, classForm->relkind,
    +									   datum, false);
     			break;
     		case RELKIND_PARTITIONED_TABLE:
     			options = partitioned_table_reloptions(datum, false);
    @@ -2040,7 +2043,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 the implementation
    + * of relOptions for the access method heap.
      */
     bytea *
     heap_reloptions(char relkind, Datum reloptions, bool validate)
    @@ -2070,6 +2074,62 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
     }
     
     
    +/*
    + * Parse options for tables.
    + *
    + *	reloptsfun	Table AM's option parser function. Can be NULL if amid is
    + *				valid. In this case we load the new table AM and use its option
    + *				parser function.
    + *	amid		New table AM's Oid if any.
    + *	relkind		relation kind
    + *	reloptions	options as text[] datum
    + *	validate	error flag
    + */
    +bytea *
    +table_reloptions(reloptions_function reloptsfun, Oid amid, char relkind,
    +				 Datum reloptions, bool validate)
    +{
    +	/* amid and reloptsfun are mutually exclusive */
    +	Assert((!OidIsValid(amid) && (reloptsfun != NULL)) || \
    +		   (OidIsValid(amid) && (reloptsfun == NULL)));
    +
    +	/* Parse/validate options using reloptsfun */
    +	if (!OidIsValid(amid) && reloptsfun != NULL)
    +	{
    +		/* Assume function is strict */
    +		if (!PointerIsValid(DatumGetPointer(reloptions)))
    +			return NULL;
    +
    +		return reloptsfun(relkind, reloptions, validate);
    +	}
    +	/* Parse/validate options using the API of the new Table AM */
    +	else if (OidIsValid(amid) && (reloptsfun == NULL))
    +	{
    +		const TableAmRoutine *routine;
    +		HeapTuple	atuple;
    +		Form_pg_am	aform;
    +
    +		atuple = SearchSysCache1(AMOID, ObjectIdGetDatum(amid));
    +
    +		if (!HeapTupleIsValid(atuple))
    +			elog(ERROR, "cache lookup failed for access method %u", amid);
    +
    +		aform = (Form_pg_am) GETSTRUCT(atuple);
    +		routine = GetTableAmRoutine(aform->amhandler);
    +		ReleaseSysCache(atuple);
    +
    +		if (routine->relation_options != NULL)
    +			return routine->relation_options(relkind, reloptions, validate);
    +
    +		return NULL;
    +	}
    +	else
    +	{
    +		/* Should not happen */
    +		return NULL;
    +	}
    +}
    +
     /*
      * Parse options for indexes.
      *
    diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
    index ac082fefa77..79f176bb7b8 100644
    --- a/src/backend/access/heap/heapam_handler.c
    +++ b/src/backend/access/heap/heapam_handler.c
    @@ -24,6 +24,7 @@
     #include "access/heaptoast.h"
     #include "access/multixact.h"
     #include "access/rewriteheap.h"
    +#include "access/reloptions.h"
     #include "access/syncscan.h"
     #include "access/tableam.h"
     #include "access/tsmapi.h"
    @@ -2659,6 +2660,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/foreigncmds.c b/src/backend/commands/foreigncmds.c
    index c14e038d54f..9dab5dfb999 100644
    --- a/src/backend/commands/foreigncmds.c
    +++ b/src/backend/commands/foreigncmds.c
    @@ -62,7 +62,7 @@ static void import_error_callback(void *arg);
      * processing, hence any validation should be done before this
      * conversion.
      */
    -static Datum
    +Datum
     optionListToArray(List *options)
     {
     	ArrayBuildState *astate = NULL;
    diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
    index 54ad38247aa..64b10a58d1c 100644
    --- a/src/backend/commands/tablecmds.c
    +++ b/src/backend/commands/tablecmds.c
    @@ -677,6 +677,8 @@ 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 ATExecSetAccessMethodOptions(Relation rel, List *defList, AlterTableType operation,
    +										 LOCKMODE lockmode, Oid newAccessMethodId);
     static void ATExecSetRelOptions(Relation rel, List *defList,
     								AlterTableType operation,
     								LOCKMODE lockmode);
    @@ -926,24 +928,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;
    @@ -1046,6 +1030,29 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
     			accessMethodId = get_table_am_oid(default_table_access_method, 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:
    +			(void) table_reloptions(NULL, accessMethodId, relkind, reloptions,
    +									true);
    +			break;
    +		default:
    +			(void) heap_reloptions(relkind, reloptions, true);
    +	}
    +
     	/*
     	 * Create the relation.  Inherited defaults and CHECK constraints are
     	 * passed in for immediate handling --- since they don't need parsing,
    @@ -5527,6 +5534,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
     			if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
     				tab->chgAccessMethod)
     				ATExecSetAccessMethodNoStorage(rel, tab->newAccessMethod);
    +
    +			ATExecSetAccessMethodOptions(rel, (List *) cmd->def, cmd->subtype,
    +										 lockmode, tab->newAccessMethod);
     			break;
     		case AT_SetTableSpace:	/* SET TABLESPACE */
     
    @@ -16550,6 +16560,138 @@ ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, const char *tablespacen
     	tab->newTableSpace = tablespaceId;
     }
     
    +/* SET, ADD or DROP options in ALTER TABLE SET ACCESS METHOD */
    +static void
    +ATExecSetAccessMethodOptions(Relation rel, List *options, AlterTableType operation,
    +							 LOCKMODE lockmode, Oid newAccessMethodId)
    +{
    +	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;
    +
    +	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);
    +
    +	if (isnull)
    +		datum = PointerGetDatum(NULL);
    +
    +	resultOptions = untransformRelOptions(datum);
    +
    +	foreach(optcell, options)
    +	{
    +		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 SET/DROP actions on the same
    +		 * option.  The standard permits this, as long as the options to be
    +		 * added are unique.  Note that an unspecified action is taken to be
    +		 * ADD.
    +		 */
    +		switch (od->defaction)
    +		{
    +			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;
    +
    +			case DEFELEM_SET:
    +				if (!cell)
    +					ereport(ERROR,
    +							(errcode(ERRCODE_UNDEFINED_OBJECT),
    +							 errmsg("option \"%s\" not found",
    +									od->defname)));
    +				lfirst(cell) = od;
    +				break;
    +
    +			case DEFELEM_ADD:
    +			case DEFELEM_UNSPEC:
    +				if (cell)
    +					ereport(ERROR,
    +							(errcode(ERRCODE_DUPLICATE_OBJECT),
    +							 errmsg("option \"%s\" provided more than once",
    +									od->defname)));
    +				resultOptions = lappend(resultOptions, od);
    +				break;
    +
    +			default:
    +				elog(ERROR, "unrecognized action %d on option \"%s\"",
    +					 (int) od->defaction, od->defname);
    +				break;
    +		}
    +	}
    +
    +	newOptions = optionListToArray(resultOptions);
    +
    +	/*
    +	 * If the new table access method was not explicitly defined, then use the
    +	 * default one.
    +	 */
    +	if (!OidIsValid(newAccessMethodId))
    +		newAccessMethodId = get_table_am_oid(default_table_access_method, false);
    +
    +	/* Validate new options via the new Table Access Method API */
    +	(void) table_reloptions(NULL, newAccessMethodId, rel->rd_rel->relkind,
    +							newOptions, true);
    +
    +	/* Initialize buffers for new tuple values */
    +	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;
    +
    +	/* Everything looks good - update the tuple */
    +	newtuple = heap_modify_tuple(tuple, RelationGetDescr(pgclass),
    +								 repl_val, repl_null, repl_repl);
    +
    +	CatalogTupleUpdate(pgclass, &newtuple->t_self, newtuple);
    +
    +	InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel),
    +							  InvalidOid);
    +
    +	ReleaseSysCache(tuple);
    +
    +	table_close(pgclass, RowExclusiveLock);
    +
    +	heap_freetuple(newtuple);
    +}
    +
     /*
      * Set, reset, or replace reloptions.
      */
    @@ -16607,7 +16749,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
     	{
     		case RELKIND_RELATION:
     		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 0b5652071d1..a96c9eb15f2 100644
    --- a/src/backend/parser/gram.y
    +++ b/src/backend/parser/gram.y
    @@ -2924,6 +2924,15 @@ alter_table_cmd:
     					n->name = $4;
     					$$ = (Node *) n;
     				}
    +			/* ALTER TABLE <name> SET ACCESS METHOD <amname> [OPTIONS]*/
    +			| SET ACCESS METHOD name alter_generic_options
    +				{
    +					AlterTableCmd *n = makeNode(AlterTableCmd);
    +					n->subtype = AT_SetAccessMethod;
    +					n->name = $4;
    +					n->def = (Node *) $5;
    +					$$ = (Node *)n;
    +				}
     			/* ALTER TABLE <name> SET TABLESPACE <tablespacename> */
     			| SET TABLESPACE name
     				{
    diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
    index 981be42e3af..202fddf2de9 100644
    --- a/src/backend/postmaster/autovacuum.c
    +++ b/src/backend/postmaster/autovacuum.c
    @@ -332,6 +332,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,
    @@ -346,7 +347,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 void perform_work_item(AutoVacuumWorkItem *workitem);
     static void autovac_report_activity(autovac_table *tab);
     static void autovac_report_workitem(AutoVacuumWorkItem *workitem,
    @@ -2033,7 +2034,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 = pgstat_fetch_stat_tabentry_ext(classForm->relisshared,
     												  relid);
     
    @@ -2113,7 +2115,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)
     			free_relopts = true;
     		else
    @@ -2379,6 +2382,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)
     		{
    @@ -2706,7 +2710,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;
    @@ -2715,7 +2720,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;
     
    @@ -2738,6 +2743,7 @@ extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc)
     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;
    @@ -2759,7 +2765,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 (avopts)
     		free_avopts = true;
     	else if (classForm->relkind == RELKIND_TOASTVALUE &&
    diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
    index 559ba9cdb2c..fb3c8ee2f64 100644
    --- a/src/backend/utils/cache/relcache.c
    +++ b/src/backend/utils/cache/relcache.c
    @@ -469,6 +469,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
     {
     	bytea	   *options;
     	amoptions_function amoptsfn;
    +	reloptions_function reloptsfn;
     
     	relation->rd_options = NULL;
     
    @@ -480,13 +481,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:
    @@ -498,7 +504,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 dfbb4c85460..37f51d0f1c2 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"
    @@ -237,7 +238,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 reloptsfun);
     extern void *build_reloptions(Datum reloptions, bool validate,
     							  relopt_kind kind,
     							  Size relopt_struct_size,
    @@ -251,6 +253,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, Oid amid, 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 8713e12cbfb..b9544173f96 100644
    --- a/src/include/access/tableam.h
    +++ b/src/include/access/tableam.h
    @@ -269,6 +269,14 @@ typedef void (*IndexBuildCallback) (Relation index,
     									bool tupleIsAlive,
     									void *state);
     
    +/*
    + * Callback in charge of parsing and validating the table reloptions.
    + * It returns parsed options 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
    @@ -708,6 +716,8 @@ typedef struct TableAmRoutine
     	 * ------------------------------------------------------------------------
     	 */
     
    +	reloptions_function relation_options;
    +
     	/*
     	 * See table_relation_size().
     	 *
    diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
    index dd22b5efdfd..8e42f394107 100644
    --- a/src/include/commands/defrem.h
    +++ b/src/include/commands/defrem.h
    @@ -136,6 +136,7 @@ extern ObjectAddress AlterUserMapping(AlterUserMappingStmt *stmt);
     extern Oid	RemoveUserMapping(DropUserMappingStmt *stmt);
     extern void CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid);
     extern void ImportForeignSchema(ImportForeignSchemaStmt *stmt);
    +extern Datum optionListToArray(List *options);
     extern Datum transformGenericOptions(Oid catalogId,
     									 Datum oldOptions,
     									 List *options,
    -- 
    2.39.5
    
    
    --kp2yjslz3dxnr4ia
    Content-Type: text/x-diff; charset=us-ascii
    Content-Disposition: attachment;
    	filename="v4-0002-Add-the-dummy_table_am-test-module.patch"