From 1af0668abbb194beb88944e9608f2142b9279976 Mon Sep 17 00:00:00 2001
From: Nikita Malakhov <n.malakhov@postgrespro.ru>
Date: Sat, 14 Jun 2025 21:02:50 +0300
Subject: [PATCH] Custom reloptions for Table AM

Currently custom reloptions could be set by Index AM,
but not Table AM. This patch introduces possibility
to define reloptions procedure like the it is introduced
in Index AM, by adding .amoptions field to TableAmRoutine
structure, and implementing <tableamoptions> function
in the corresponding Table AM.

The main scenario for using custom reloptions is creating
table with custom options and table AM, as:
CREATE TABLE t () USING myTableAM WITH (custom_opt1=..., )
and altering them with ALTER TABLE command.

Author: Nikita Malakhov
---
 src/backend/access/common/reloptions.c   |  7 ++--
 src/backend/access/heap/heapam_handler.c |  3 +-
 src/backend/commands/createas.c          |  2 +-
 src/backend/commands/tablecmds.c         | 43 ++++++++++++++++++++++--
 src/backend/tcop/utility.c               |  3 +-
 src/include/access/reloptions.h          |  2 +-
 src/include/access/tableam.h             |  3 +-
 7 files changed, 53 insertions(+), 10 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 50747c16396..eae86fea409 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -1419,7 +1419,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
-			options = heap_reloptions(classForm->relkind, datum, false);
+			options = heap_reloptions(amoptions, classForm->relkind, datum, false);
 			break;
 		case RELKIND_PARTITIONED_TABLE:
 			options = partitioned_table_reloptions(datum, false);
@@ -2052,7 +2052,7 @@ view_reloptions(Datum reloptions, bool validate)
  * Parse options for heaps, views and toast tables.
  */
 bytea *
-heap_reloptions(char relkind, Datum reloptions, bool validate)
+heap_reloptions(amoptions_function amoptions, char relkind, Datum reloptions, bool validate)
 {
 	StdRdOptions *rdopts;
 
@@ -2071,6 +2071,9 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+			if(amoptions)
+				return amoptions(reloptions, validate);
+
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index cb4bc35c93e..800eb81ae72 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -2668,7 +2668,8 @@ static const TableAmRoutine heapam_methods = {
 
 	.scan_bitmap_next_tuple = heapam_scan_bitmap_next_tuple,
 	.scan_sample_next_block = heapam_scan_sample_next_block,
-	.scan_sample_next_tuple = heapam_scan_sample_next_tuple
+	.scan_sample_next_tuple = heapam_scan_sample_next_tuple,
+	.amoptions = NULL
 };
 
 
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index dfd2ab8e862..97df562d244 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -127,7 +127,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
 										validnsps,
 										true, false);
 
-	(void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
+	(void) heap_reloptions(NULL, RELKIND_TOASTVALUE, toast_options, true);
 
 	NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ea96947d813..7587a56fa1f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -941,7 +941,41 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			(void) partitioned_table_reloptions(reloptions, true);
 			break;
 		default:
-			(void) heap_reloptions(relkind, reloptions, true);
+			TableAmRoutine *amroutine = NULL;
+
+			if (stmt->accessMethod != NULL)
+			{
+				Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE);
+				accessMethodId = get_table_am_oid(stmt->accessMethod, false);
+			}
+			else if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE)
+			{
+				if (stmt->partbound)
+				{
+					Assert(list_length(inheritOids) == 1);
+					accessMethodId = get_rel_relam(linitial_oid(inheritOids));
+				}
+
+				if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId))
+					accessMethodId = get_table_am_oid(default_table_access_method, false);
+			}
+
+			if(OidIsValid(accessMethodId) && accessMethodId != HEAP_TABLE_AM_OID)
+			{
+				HeapTuple	tup;
+
+				tup = SearchSysCache1(AMOID,
+										ObjectIdGetDatum(accessMethodId));
+				if (HeapTupleIsValid(tup))
+				{
+					Form_pg_am	amform = (Form_pg_am) GETSTRUCT(tup);
+
+					amroutine = unconstify(TableAmRoutine *, GetTableAmRoutine(amform->amhandler));
+
+					ReleaseSysCache(tup);
+				}
+			}
+			(void) heap_reloptions(amroutine ? amroutine->amoptions : NULL, relkind, reloptions, true);
 	}
 
 	if (stmt->ofTypename)
@@ -16633,7 +16667,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	{
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
-			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
+			(void) heap_reloptions(rel->rd_tableam->amoptions, rel->rd_rel->relkind, newOptions, true);
 			break;
 		case RELKIND_PARTITIONED_TABLE:
 			(void) partitioned_table_reloptions(newOptions, true);
@@ -16751,7 +16785,10 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 		newOptions = transformRelOptions(datum, defList, "toast", validnsps,
 										 false, operation == AT_ResetRelOptions);
 
-		(void) heap_reloptions(RELKIND_TOASTVALUE, newOptions, true);
+		/* Probably we have to always set NULL custom reloptions to TOAST tables?
+		 * (void) heap_reloptions(NULL, RELKIND_TOASTVALUE, newOptions, true);
+		 */
+		(void) heap_reloptions(rel->rd_tableam->amoptions, RELKIND_TOASTVALUE, newOptions, true);
 
 		memset(repl_val, 0, sizeof(repl_val));
 		memset(repl_null, false, sizeof(repl_null));
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 25fe3d58016..2454a4e66f8 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1188,7 +1188,8 @@ ProcessUtilitySlow(ParseState *pstate,
 																validnsps,
 																true,
 																false);
-							(void) heap_reloptions(RELKIND_TOASTVALUE,
+
+							(void) heap_reloptions(NULL, RELKIND_TOASTVALUE,
 												   toast_options,
 												   true);
 
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index dfbb4c85460..b8ba793cc8a 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -248,7 +248,7 @@ extern void *build_local_reloptions(local_relopts *relopts, Datum options,
 
 extern bytea *default_reloptions(Datum reloptions, bool validate,
 								 relopt_kind kind);
-extern bytea *heap_reloptions(char relkind, Datum reloptions, bool validate);
+extern bytea *heap_reloptions(amoptions_function amoptions, 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,
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 8713e12cbfb..97573931bb2 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -17,6 +17,7 @@
 #ifndef TABLEAM_H
 #define TABLEAM_H
 
+#include "access/amapi.h"
 #include "access/relscan.h"
 #include "access/sdir.h"
 #include "access/xact.h"
@@ -834,7 +835,7 @@ typedef struct TableAmRoutine
 	bool		(*scan_sample_next_tuple) (TableScanDesc scan,
 										   struct SampleScanState *scanstate,
 										   TupleTableSlot *slot);
-
+	amoptions_function amoptions;
 } TableAmRoutine;
 
 
-- 
2.34.1

