From 7b8b9afcd2c528f7bf826c7596e7e35e7c8f0aa1 Mon Sep 17 00:00:00 2001
From: B Sadhu Prasad Patro <b.sadhuprasadp@enterprisedb.com>
Date: Wed, 23 Feb 2022 21:59:47 -0800
Subject: [PATCH v3] [PATCH V3] 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.
---
 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                   |  66 ++-
 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 +-
 src/test/modules/dummy_table_am/Makefile           |  20 +
 src/test/modules/dummy_table_am/README             |  12 +
 .../modules/dummy_table_am/dummy_table_am--1.0.sql |  19 +
 src/test/modules/dummy_table_am/dummy_table_am.c   | 491 +++++++++++++++++++++
 .../modules/dummy_table_am/dummy_table_am.control  |   6 +
 .../modules/dummy_table_am/expected/reloptions.out |  10 +
 src/test/modules/dummy_table_am/sql/reloptions.sql |  14 +
 15 files changed, 682 insertions(+), 33 deletions(-)
 create mode 100644 src/test/modules/dummy_table_am/Makefile
 create mode 100644 src/test/modules/dummy_table_am/README
 create mode 100644 src/test/modules/dummy_table_am/dummy_table_am--1.0.sql
 create mode 100644 src/test/modules/dummy_table_am/dummy_table_am.c
 create mode 100644 src/test/modules/dummy_table_am/dummy_table_am.control
 create mode 100644 src/test/modules/dummy_table_am/expected/reloptions.out
 create mode 100644 src/test/modules/dummy_table_am/sql/reloptions.sql

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 3e83f37..1d6168f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -809,24 +809,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;
@@ -947,6 +929,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.
@@ -14137,7 +14165,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/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 */
diff --git a/src/test/modules/dummy_table_am/Makefile b/src/test/modules/dummy_table_am/Makefile
new file mode 100644
index 0000000..94837df
--- /dev/null
+++ b/src/test/modules/dummy_table_am/Makefile
@@ -0,0 +1,20 @@
+# src/test/modules/dummy_table_am/Makefile
+
+MODULES = dummy_table_am
+
+EXTENSION = dummy_table_am
+DATA = dummy_table_am--1.0.sql
+PGFILEDESC = "dummy_table_am - table access method template"
+
+REGRESS = reloptions
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/dummy_table_am
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/dummy_table_am/README b/src/test/modules/dummy_table_am/README
new file mode 100644
index 0000000..c1a9642
--- /dev/null
+++ b/src/test/modules/dummy_table_am/README
@@ -0,0 +1,12 @@
+Dummy Table AM
+==============
+
+Dummy table AM is a module for testing any facility usable by an table
+access method, whose code is kept a maximum simple.
+
+This includes tests for all relation option types:
+- boolean
+- enum
+- integer
+- real
+- strings (with and without NULL as default)
diff --git a/src/test/modules/dummy_table_am/dummy_table_am--1.0.sql b/src/test/modules/dummy_table_am/dummy_table_am--1.0.sql
new file mode 100644
index 0000000..aca35b7
--- /dev/null
+++ b/src/test/modules/dummy_table_am/dummy_table_am--1.0.sql
@@ -0,0 +1,19 @@
+/* src/test/modules/dummy_table_am/dummy_table_am--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION dummy_table_am" to load this file. \quit
+
+CREATE FUNCTION dthandler(internal)
+RETURNS table_am_handler
+AS 'MODULE_PATHNAME'
+LANGUAGE C;
+
+-- Access method
+CREATE ACCESS METHOD dummy_table_am TYPE TABLE HANDLER dthandler;
+COMMENT ON ACCESS METHOD dummy_table_am IS 'dummy table access method';
+
+-- Operator classes
+--CREATE OPERATOR CLASS int4_ops
+--DEFAULT FOR TYPE int4 USING dummy_table_am AS
+--  OPERATOR 1 = (int4, int4),
+-- FUNCTION 1 hashint4(int4);
diff --git a/src/test/modules/dummy_table_am/dummy_table_am.c b/src/test/modules/dummy_table_am/dummy_table_am.c
new file mode 100644
index 0000000..fb3c0ae
--- /dev/null
+++ b/src/test/modules/dummy_table_am/dummy_table_am.c
@@ -0,0 +1,491 @@
+/*-------------------------------------------------------------------------
+ *
+ * dummy_table_am.c
+ *              Table AM template main file.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *        src/test/modules/dummy_tablen_am/dummy_table_am.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/reloptions.h"
+#include "catalog/index.h"
+#include "commands/vacuum.h"
+#include "nodes/pathnodes.h"
+#include "utils/guc.h"
+#include "utils/rel.h"
+
+PG_MODULE_MAGIC;
+
+void            _PG_init(void);
+
+/* parse table for fillRelOptions */
+relopt_parse_elt di_relopt_tab[2];
+
+/* Dummy table options */
+typedef struct DummyTableOptions
+{
+	StdRdOptions	stdOptions;
+        bool		isCompression;
+}DummyTableOptions;
+
+typedef struct BulkInsertStateData *BulkInsertState;
+
+/* Handler for table AM */
+PG_FUNCTION_INFO_V1(dthandler);
+
+/*
+ * This function creates a full set of relation option types,
+ * with various patterns.
+ */
+static bytea *
+create_reloptions_table(char relkind, Datum reloptions, bool validate)
+{
+	DummyTableOptions* options = (DummyTableOptions *)DatumGetPointer(reloptions);
+	if (options->isCompression)
+	{
+		printf("COMPRESSION ENABLED");
+	}
+	else
+	{
+		printf("COMPRESSION DISABLED");
+	}
+
+	return NULL;
+}
+
+static bool
+tblam_scan_bitmap_next_block(TableScanDesc scan,
+							  TBMIterateResult *tbmres)
+{
+	return false;
+}
+
+static bool
+tblam_scan_bitmap_next_tuple(TableScanDesc scan,
+							  TBMIterateResult *tbmres,
+							  TupleTableSlot *slot)
+{
+	return false;
+}
+
+static bool
+tblam_scan_sample_next_block(TableScanDesc scan, SampleScanState *scanstate)
+{
+	return false;
+}
+
+static bool
+tblam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
+							  TupleTableSlot *slot)
+{
+	return false;
+}
+
+static void
+tblam_estimate_rel_size(Relation rel, int32 *attr_widths,
+						 BlockNumber *pages, double *tuples,
+						 double *allvisfrac)
+{
+	*allvisfrac = 10 * 8192 /* assuming BLCKSZ */;
+	return;
+}
+
+static void
+tblam_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize,
+                                           int32 sliceoffset, int32 slicelength,
+                                           struct varlena *result)
+{
+	return;
+}
+
+static Oid
+tblam_relation_toast_am(Relation rel)
+{
+	return rel->rd_rel->relam;
+}
+
+static bool
+tblam_relation_needs_toast_table(Relation rel)
+{
+	return false;
+}
+
+static uint64
+tblam_block_relation_size(Relation rel, ForkNumber forkNumber)
+{
+	uint64		nblocks = 10;
+	return nblocks * 8192 /* assuming BLCKSZ*/;
+}
+
+static void
+tblam_index_validate_scan(Relation heapRelation,
+						   Relation indexRelation,
+						   IndexInfo *indexInfo,
+						   Snapshot snapshot,
+						   ValidateIndexState *state)
+{
+	return;
+}
+
+static double
+tblam_index_build_range_scan(Relation heapRelation,
+							  Relation indexRelation,
+							  IndexInfo *indexInfo,
+							  bool allow_sync,
+							  bool anyvisible,
+							  bool progress,
+							  BlockNumber start_blockno,
+							  BlockNumber numblocks,
+							  IndexBuildCallback callback,
+							  void *callback_state,
+							  TableScanDesc scan)
+{
+	return 0;
+}
+
+static bool
+tblam_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin,
+							   double *liverows, double *deadrows,
+							   TupleTableSlot *slot)
+{
+	return false;
+}
+
+static void
+tblam_vacuum_rel(Relation rel, VacuumParams *params,
+                                BufferAccessStrategy bstrategy)
+{
+	return;
+}
+
+static bool
+tblam_scan_analyze_next_block(TableScanDesc scan, BlockNumber blockno,
+							   BufferAccessStrategy bstrategy)
+{
+	return false;
+}
+
+static void
+tblam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
+								 Relation OldIndex, bool use_sort,
+								 TransactionId OldestXmin,
+								 TransactionId *xid_cutoff,
+								 MultiXactId *multi_cutoff,
+								 double *num_tuples,
+								 double *tups_vacuumed,
+								 double *tups_recently_dead)
+{
+	return;
+}
+
+static void
+tblam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
+{
+	return;
+}
+
+static void
+tblam_relation_nontransactional_truncate(Relation rel)
+{
+	return;
+}
+
+static void
+tblam_relation_set_new_filenode(Relation rel,
+								 const RelFileNode *newrnode,
+								 char persistence,
+								 TransactionId *freezeXid,
+								 MultiXactId *minmulti)
+{
+	return;
+}
+
+static TransactionId
+tblam_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate)
+{
+	return InvalidTransactionId;
+}
+
+static bool
+tblam_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot,
+								Snapshot snapshot)
+{
+	return false;
+}
+
+static bool
+tblam_tuple_tid_valid(TableScanDesc scan, ItemPointer tid)
+{
+	return false;
+}
+
+static void
+tblam_get_latest_tid(TableScanDesc sscan,
+					ItemPointer tid)
+{
+	return;
+}
+
+static bool
+tblam_fetch_row_version(Relation relation,
+						 ItemPointer tid,
+						 Snapshot snapshot,
+						 TupleTableSlot *slot)
+{
+	return false;
+}
+
+static TM_Result
+tblam_tuple_lock(Relation relation, ItemPointer tid, Snapshot snapshot,
+				  TupleTableSlot *slot, CommandId cid, LockTupleMode mode,
+				  LockWaitPolicy wait_policy, uint8 flags,
+				  TM_FailureData *tmfd)
+{
+	return TM_Ok;
+}
+
+static TM_Result
+tblam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
+					CommandId cid, Snapshot snapshot, Snapshot crosscheck,
+					bool wait, TM_FailureData *tmfd,
+					LockTupleMode *lockmode, bool *update_indexes)
+{
+	return TM_Ok;
+}
+
+static TM_Result
+tblam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
+					Snapshot snapshot, Snapshot crosscheck, bool wait,
+					TM_FailureData *tmfd, bool changingPart)
+{
+	return TM_Ok;
+}
+
+static void
+tblam_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
+				  CommandId cid, int options, BulkInsertState bistate)
+{
+	return;
+}
+
+static void
+tblam_tuple_complete_speculative(Relation relation, TupleTableSlot *slot,
+								  uint32 specToken, bool succeeded)
+{
+	return;
+}
+
+static void
+tblam_tuple_insert_speculative(Relation relation, TupleTableSlot *slot,
+								CommandId cid, int options,
+								BulkInsertState bistate, uint32 specToken)
+{
+	return;
+}
+
+static void
+tblam_tuple_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
+					int options, BulkInsertState bistate)
+{
+	return;
+}
+
+static IndexFetchTableData *
+tblam_index_fetch_begin(Relation rel)
+{
+	return NULL;
+}
+
+static void
+tblam_index_fetch_reset(IndexFetchTableData *scan)
+{
+	return;
+}
+
+static void
+tblam_index_fetch_end(IndexFetchTableData *scan)
+{
+	return;
+}
+
+static bool
+tblam_index_fetch_tuple(struct IndexFetchTableData *scan,
+						 ItemPointer tid,
+						 Snapshot snapshot,
+						 TupleTableSlot *slot,
+						 bool *call_again, bool *all_dead)
+{
+	return false;
+}
+
+static Size
+tblam_block_parallelscan_estimate(Relation rel)
+{
+	return sizeof(ParallelBlockTableScanDescData);
+}
+
+static Size
+tblam_block_parallelscan_initialize(Relation rel, ParallelTableScanDesc pscan)
+{
+	ParallelBlockTableScanDesc bpscan = (ParallelBlockTableScanDesc) pscan;
+
+	bpscan->base.phs_relid = RelationGetRelid(rel);
+	bpscan->phs_nblocks = 10; //RelationGetNumberOfBlocks(rel);
+	/* compare phs_syncscan initialization to similar logic in initscan */
+	bpscan->base.phs_syncscan = synchronize_seqscans &&
+		!RelationUsesLocalBuffers(rel) &&
+		bpscan->phs_nblocks > 10 / 4;
+	SpinLockInit(&bpscan->phs_mutex);
+	bpscan->phs_startblock = InvalidBlockNumber;
+	pg_atomic_init_u64(&bpscan->phs_nallocated, 0);
+
+	return sizeof(ParallelBlockTableScanDescData);
+}
+
+static void
+tblam_block_parallelscan_reinitialize(Relation rel, ParallelTableScanDesc pscan)
+{
+	ParallelBlockTableScanDesc bpscan = (ParallelBlockTableScanDesc) pscan;
+
+	pg_atomic_write_u64(&bpscan->phs_nallocated, 0);
+}
+
+static bool
+tblam_getnextslot_tidrange(TableScanDesc sscan, ScanDirection direction,
+						  TupleTableSlot *slot)
+{
+	return false;
+}
+
+static void
+tblam_set_tidrange(TableScanDesc sscan, ItemPointer mintid,
+				  ItemPointer maxtid)
+{
+	return;
+}
+
+static bool
+tblam_getnextslot(TableScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+{
+	return false;
+}
+
+static void
+tblam_rescan(TableScanDesc sscan, ScanKey key, bool set_params,
+			bool allow_strat, bool allow_sync, bool allow_pagemode)
+{
+	return;
+}
+
+static void
+tblam_endscan(TableScanDesc sscan)
+{
+	return;
+}
+
+static TableScanDesc
+tblam_beginscan(Relation relation, Snapshot snapshot,
+			   int nkeys, ScanKey key,
+			   ParallelTableScanDesc parallel_scan,
+			   uint32 flags)
+{
+	return NULL;
+}
+
+static const TupleTableSlotOps *
+tblam_slot_callbacks(Relation relation)
+{
+	return &TTSOpsBufferHeapTuple;
+}
+
+/*
+ * Table AM handler function: returns TableAmRoutine with access method
+ * parameters and callbacks.
+ */
+Datum
+dthandler(PG_FUNCTION_ARGS)
+{
+	TableAmRoutine *amroutine = makeNode(TableAmRoutine);
+
+	amroutine->type = T_TableAmRoutine;
+
+	amroutine->slot_callbacks = tblam_slot_callbacks;
+
+	amroutine->scan_begin = tblam_beginscan;
+	amroutine->scan_end = tblam_endscan;
+	amroutine->scan_rescan = tblam_rescan;
+	amroutine->scan_getnextslot = tblam_getnextslot;
+
+	amroutine->scan_set_tidrange = tblam_set_tidrange;
+	amroutine->scan_getnextslot_tidrange = tblam_getnextslot_tidrange;
+
+	amroutine->parallelscan_estimate = tblam_block_parallelscan_estimate;
+	amroutine->parallelscan_initialize = tblam_block_parallelscan_initialize;
+	amroutine->parallelscan_reinitialize = tblam_block_parallelscan_reinitialize;
+
+	amroutine->index_fetch_begin = tblam_index_fetch_begin;
+	amroutine->index_fetch_reset = tblam_index_fetch_reset;
+	amroutine->index_fetch_end = tblam_index_fetch_end;
+	amroutine->index_fetch_tuple = tblam_index_fetch_tuple;
+
+	amroutine->tuple_insert = tblam_tuple_insert;
+	amroutine->tuple_insert_speculative = tblam_tuple_insert_speculative;
+	amroutine->tuple_complete_speculative = tblam_tuple_complete_speculative;
+	amroutine->multi_insert = tblam_multi_insert;
+	amroutine->tuple_delete = tblam_tuple_delete;
+	amroutine->tuple_update = tblam_tuple_update;
+	amroutine->tuple_lock = tblam_tuple_lock;
+
+	amroutine->tuple_fetch_row_version = tblam_fetch_row_version;
+	amroutine->tuple_get_latest_tid = tblam_get_latest_tid;
+	amroutine->tuple_tid_valid = tblam_tuple_tid_valid;
+	amroutine->tuple_satisfies_snapshot = tblam_tuple_satisfies_snapshot;
+	amroutine->index_delete_tuples = tblam_index_delete_tuples;
+
+	amroutine->relation_set_new_filenode = tblam_relation_set_new_filenode;
+	amroutine->relation_nontransactional_truncate = tblam_relation_nontransactional_truncate;
+	amroutine->relation_copy_data = tblam_relation_copy_data;
+	amroutine->relation_copy_for_cluster = tblam_relation_copy_for_cluster;
+	amroutine->relation_vacuum = tblam_vacuum_rel;
+	amroutine->scan_analyze_next_block = tblam_scan_analyze_next_block;
+	amroutine->scan_analyze_next_tuple = tblam_scan_analyze_next_tuple;
+	amroutine->index_build_range_scan = tblam_index_build_range_scan;
+	amroutine->index_validate_scan = tblam_index_validate_scan;
+
+	amroutine->relation_options = create_reloptions_table;
+	amroutine->relation_size = tblam_block_relation_size;
+	amroutine->relation_needs_toast_table = tblam_relation_needs_toast_table;
+	amroutine->relation_toast_am = tblam_relation_toast_am;
+	amroutine->relation_fetch_toast_slice = tblam_fetch_toast_slice;
+
+	amroutine->relation_estimate_size = tblam_estimate_rel_size;
+
+	amroutine->scan_bitmap_next_block = tblam_scan_bitmap_next_block;
+	amroutine->scan_bitmap_next_tuple = tblam_scan_bitmap_next_tuple;
+	amroutine->scan_sample_next_block = tblam_scan_sample_next_block;
+	amroutine->scan_sample_next_tuple = tblam_scan_sample_next_tuple;
+
+	PG_RETURN_POINTER(amroutine);
+}
+
+void
+_PG_init(void)
+{
+	DummyTableOptions tabAmStr = {{0}};
+
+	tabAmStr.isCompression = false;
+        create_reloptions_table(RELKIND_RELATION, (Datum)&tabAmStr, false);
+
+	{
+		Relation rel;
+		tblam_relation_set_new_filenode(rel, NULL, 'p', NULL, NULL);
+	}
+}
+
diff --git a/src/test/modules/dummy_table_am/dummy_table_am.control b/src/test/modules/dummy_table_am/dummy_table_am.control
new file mode 100644
index 0000000..a81d361
--- /dev/null
+++ b/src/test/modules/dummy_table_am/dummy_table_am.control
@@ -0,0 +1,6 @@
+# dummy_table_am extension
+comment = 'dummy_table_am - table access method template'
+default_version = '1.0'
+module_pathname = '$libdir/dummy_table_am'
+relocatable = true
+
diff --git a/src/test/modules/dummy_table_am/expected/reloptions.out b/src/test/modules/dummy_table_am/expected/reloptions.out
new file mode 100644
index 0000000..2e75881
--- /dev/null
+++ b/src/test/modules/dummy_table_am/expected/reloptions.out
@@ -0,0 +1,10 @@
+-- Tests for relation options
+CREATE EXTENSION dummy_table_am;
+CREATE TABLE dummy_test_tab (i int4);
+-- Silence validation checks for strings
+SET client_min_messages TO 'warning';
+-- Test for new AM for dummy_table_am
+CREATE TABLE mytest (a int) USING dummy_table_am WITH (is_compression='TRUE');
+ALTER TABLE mytest SET (isCompression = 'FALSE');
+insert into mytest values(10);
+DROP TABLE mytest;
diff --git a/src/test/modules/dummy_table_am/sql/reloptions.sql b/src/test/modules/dummy_table_am/sql/reloptions.sql
new file mode 100644
index 0000000..7eb0372
--- /dev/null
+++ b/src/test/modules/dummy_table_am/sql/reloptions.sql
@@ -0,0 +1,14 @@
+-- Tests for relation options
+CREATE EXTENSION dummy_table_am;
+CREATE TABLE dummy_test_tab (i int4);
+
+-- Silence validation checks for strings
+SET client_min_messages TO 'warning';
+
+-- Test for new AM for dummy_table_am
+CREATE TABLE mytest (a int) USING dummy_table_am WITH (is_compression='TRUE');
+ALTER TABLE mytest SET (isCompression = 'FALSE');
+
+insert into mytest values(10);
+
+DROP TABLE mytest;
-- 
1.8.3.1

