Custom reloptions in TableAM

Started by Nikita Malakhov7 months ago4 messages
#1Nikita Malakhov
hukutoc@gmail.com
1 attachment(s)

Hi hackers!

While developing an alternative Table AM I've stumbled
upon the impossibility to set custom reloptions to the table
created by Table AM, like it could be done for Index AM.

I haven't found any comments on this and implemented
a mechanism of setting custom reloptions for a relation
created with Table AM, if needed. These options are stored
in Relcache and are accessed through the rd_options field.

The main scenario for custom reloptions is something like:
CREATE TABLE t (...) USING myTableAM WITH (custom_opt1=..., ...);

and myTableAM should implement custom options function,
like Index AM, and assign in to an amoptions field:

bytea *myamoptions(Datum reloptions, bool validate)
{
myAMOptions *rdopts;
static const relopt_parse_elt tab[] = {
{"custom_opt1", RELOPT_TYPE_INT, offsetof(myAMOptions, customOpt1)},
...
};

/* Parse the user-given reloptions */
rdopts = (myAMOptions *) build_reloptions(reloptions, validate,
my_relopt_kind,
sizeof(myAMOptions),
tab,
lengthof(tab));

return (bytea *) rdopts;
}

Hope you'll find it useful.
Advice and objections are welcome.
POC patch in attachment.

--
Regards,
Nikita Malakhov
Postgres Professional
The Russian Postgres Company
https://postgrespro.ru/

Attachments:

v1-0001-tableam-relopts.patchapplication/octet-stream; name=v1-0001-tableam-relopts.patchDownload
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

#2Srinath Reddy Sadipiralla
srinath2133@gmail.com
In reply to: Nikita Malakhov (#1)
Re: Custom reloptions in TableAM

On Sat, Jun 21, 2025 at 1:39 AM Nikita Malakhov <hukutoc@gmail.com> wrote:

Hi hackers!

While developing an alternative Table AM I've stumbled
upon the impossibility to set custom reloptions to the table
created by Table AM, like it could be done for Index AM.

I haven't found any comments on this and implemented
a mechanism of setting custom reloptions for a relation
created with Table AM, if needed. These options are stored
in Relcache and are accessed through the rd_options field.

The main scenario for custom reloptions is something like:
CREATE TABLE t (...) USING myTableAM WITH (custom_opt1=..., ...);

I think there are some similar efforts going on ,you can check in this
thread [0]/messages/by-id/20250302085641.hmjom5ru3w554t2n@poseidon.home.virt.

[0]: /messages/by-id/20250302085641.hmjom5ru3w554t2n@poseidon.home.virt
/messages/by-id/20250302085641.hmjom5ru3w554t2n@poseidon.home.virt

--
Thanks,
Srinath Reddy Sadipiralla
EDB: https://www.enterprisedb.com/

#3Nikita Malakhov
hukutoc@gmail.com
In reply to: Srinath Reddy Sadipiralla (#2)
Re: Custom reloptions in TableAM

Hi!

Thank you very much, I'll check it out.

--
Regards,
Nikita Malakhov
Postgres Professional
The Russian Postgres Company
https://postgrespro.ru/

#4Michael Paquier
michael@paquier.xyz
In reply to: Nikita Malakhov (#1)
Re: Custom reloptions in TableAM

On Fri, Jun 20, 2025 at 11:09:01PM +0300, Nikita Malakhov wrote:

I haven't found any comments on this and implemented
a mechanism of setting custom reloptions for a relation
created with Table AM, if needed. These options are stored
in Relcache and are accessed through the rd_options field.

Your patch touches the backend, but there is no real way to check
whether it is correct. I would suggest revisiting an idea of dummy
table AM module added to src/test/modules/. This has been proposed
one year ago if I recall correctly on pgsql-hackers, and I did argue
that there was little value in having it as long as it does not
provide specific test coverage. The additions you are suggesting to
would be a reason enough for having such a module, IMO, validating the
correctness of what you are proposing.
--
Michael