Hi all,

During the PGCon Unconference session about Table Access Method one missing
item pointed out is that currently we lack documentation and examples of
TAM.

So in order to improve things a bit in this area I'm proposing to add a
test module for Table Access Method similar what we already have for Index
Access Method.

This code is based on the "blackhole_am" implemented by Michael Paquier:
https://github.com/michaelpq/pg_plugins/tree/main/blackhole_am

Regards,

-- 
Fabrízio de Royes Mello
From 217b84f21ec1cdb0ede271d24b7a5863713db949 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fabr=C3=ADzio=20de=20Royes=20Mello?=
 <fabriziome...@gmail.com>
Date: Sat, 3 Jun 2023 17:23:05 -0400
Subject: [PATCH] Add test module for Table Access Method

---
 src/test/modules/Makefile                     |   1 +
 src/test/modules/dummy_table_am/.gitignore    |   3 +
 src/test/modules/dummy_table_am/Makefile      |  20 +
 src/test/modules/dummy_table_am/README        |  12 +
 .../dummy_table_am/dummy_table_am--1.0.sql    |  14 +
 .../modules/dummy_table_am/dummy_table_am.c   | 519 ++++++++++++++++++
 .../dummy_table_am/dummy_table_am.control     |   5 +
 .../expected/dummy_table_am.out               | 127 +++++
 src/test/modules/dummy_table_am/meson.build   |  33 ++
 .../dummy_table_am/sql/dummy_table_am.sql     |  34 ++
 src/test/modules/meson.build                  |   1 +
 11 files changed, 769 insertions(+)
 create mode 100644 src/test/modules/dummy_table_am/.gitignore
 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/dummy_table_am.out
 create mode 100644 src/test/modules/dummy_table_am/meson.build
 create mode 100644 src/test/modules/dummy_table_am/sql/dummy_table_am.sql

diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 6331c976dc..ce982b0e46 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -10,6 +10,7 @@ SUBDIRS = \
 		  delay_execution \
 		  dummy_index_am \
 		  dummy_seclabel \
+		  dummy_table_am \
 		  libpq_pipeline \
 		  plsample \
 		  snapshot_too_old \
diff --git a/src/test/modules/dummy_table_am/.gitignore b/src/test/modules/dummy_table_am/.gitignore
new file mode 100644
index 0000000000..44d119cfcc
--- /dev/null
+++ b/src/test/modules/dummy_table_am/.gitignore
@@ -0,0 +1,3 @@
+# Generated subdirectories
+/log/
+/results/
diff --git a/src/test/modules/dummy_table_am/Makefile b/src/test/modules/dummy_table_am/Makefile
new file mode 100644
index 0000000000..9ea4a590c6
--- /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 = dummy_table_am
+
+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 0000000000..61510f02fa
--- /dev/null
+++ b/src/test/modules/dummy_table_am/README
@@ -0,0 +1,12 @@
+Dummy Index AM
+==============
+
+Dummy index AM is a module for testing any facility usable by an index
+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 0000000000..aa0fd82e61
--- /dev/null
+++ b/src/test/modules/dummy_table_am/dummy_table_am--1.0.sql
@@ -0,0 +1,14 @@
+/* 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 dummy_table_am_handler(internal)
+RETURNS table_am_handler
+AS 'MODULE_PATHNAME'
+LANGUAGE C;
+
+-- Access method
+CREATE ACCESS METHOD dummy_table_am TYPE TABLE HANDLER dummy_table_am_handler;
+COMMENT ON ACCESS METHOD dummy_table_am IS 'dummy table access method';
+
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 0000000000..b299fe9c65
--- /dev/null
+++ b/src/test/modules/dummy_table_am/dummy_table_am.c
@@ -0,0 +1,519 @@
+/*-------------------------------------------------------------------------
+ *
+ * dummy_table_am.c
+ *		Index AM template main file.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/test/modules/dummy_table_am/dummy_table_am.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/tableam.h"
+#include "catalog/index.h"
+#include "commands/vacuum.h"
+
+PG_MODULE_MAGIC;
+
+/* Handler for table AM */
+PG_FUNCTION_INFO_V1(dummy_table_am_handler);
+
+/* Base structures for scans */
+typedef struct DummyScanDescData
+{
+	TableScanDescData rs_base;
+} DummyScanDescData;
+
+typedef struct DummyScanDescData *DummyScanDesc;
+
+/*
+ * Slot related callbacks for Dummy Table Access Method
+ */
+static const TupleTableSlotOps *
+dummy_table_am_slot_callbacks(Relation relation)
+{
+	elog(INFO, "%s", __func__);
+	return &TTSOpsMinimalTuple;
+}
+
+/*
+ * Table Scan Callbacks for dummy_table_am AM
+ */
+static TableScanDesc
+dummy_table_am_scan_begin(Relation relation, Snapshot snapshot,
+					 int nkeys, ScanKey key,
+					 ParallelTableScanDesc parallel_scan,
+					 uint32 flags)
+{
+	DummyScanDesc scan;
+
+	scan = (DummyScanDesc) palloc(sizeof(DummyScanDescData));
+
+	scan->rs_base.rs_rd = relation;
+	scan->rs_base.rs_snapshot = snapshot;
+	scan->rs_base.rs_nkeys = nkeys;
+	scan->rs_base.rs_flags = flags;
+	scan->rs_base.rs_parallel = parallel_scan;
+
+	elog(INFO, "%s", __func__);
+
+	return (TableScanDesc) scan;
+}
+
+static void
+dummy_table_am_scan_end(TableScanDesc sscan)
+{
+	DummyScanDesc scan = (DummyScanDesc) sscan;
+
+	elog(INFO, "%s", __func__);
+
+	pfree(scan);
+}
+
+static void
+dummy_table_am_scan_rescan(TableScanDesc sscan, ScanKey key, bool set_params,
+					  bool allow_strat, bool allow_sync, bool allow_pagemode)
+{
+	elog(INFO, "%s", __func__);
+}
+
+static bool
+dummy_table_am_scan_getnextslot(TableScanDesc sscan, ScanDirection direction,
+						   TupleTableSlot *slot)
+{
+	elog(INFO, "%s", __func__);
+	return false;
+}
+
+/*
+ * Index Scan Callbacks for dummy_table_am AM
+ */
+static IndexFetchTableData *
+dummy_table_am_index_fetch_begin(Relation rel)
+{
+	elog(INFO, "%s", __func__);
+	return NULL;
+}
+
+static void
+dummy_table_am_index_fetch_reset(IndexFetchTableData *scan)
+{
+	elog(INFO, "%s", __func__);
+}
+
+static void
+dummy_table_am_index_fetch_end(IndexFetchTableData *scan)
+{
+	elog(INFO, "%s", __func__);
+}
+
+static bool
+dummy_table_am_index_fetch_tuple(struct IndexFetchTableData *scan,
+							ItemPointer tid,
+							Snapshot snapshot,
+							TupleTableSlot *slot,
+							bool *call_again, bool *all_dead)
+{
+	elog(INFO, "%s", __func__);
+	return 0;
+}
+
+
+/* ------------------------------------------------------------------------
+ * Callbacks for non-modifying operations on individual tuples for
+ * dummy_table_am AM.
+ * ------------------------------------------------------------------------
+ */
+
+static bool
+dummy_table_am_fetch_row_version(Relation relation,
+							ItemPointer tid,
+							Snapshot snapshot,
+							TupleTableSlot *slot)
+{
+	elog(INFO, "%s", __func__);
+	return false;
+}
+
+static void
+dummy_table_am_get_latest_tid(TableScanDesc sscan,
+						 ItemPointer tid)
+{
+	elog(INFO, "%s", __func__);
+}
+
+static bool
+dummy_table_am_tuple_tid_valid(TableScanDesc scan, ItemPointer tid)
+{
+	elog(INFO, "%s", __func__);
+	return false;
+}
+
+static bool
+dummy_table_am_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot,
+								   Snapshot snapshot)
+{
+	elog(INFO, "%s", __func__);
+	return false;
+}
+
+static TransactionId
+dummy_table_am_index_delete_tuples(Relation rel,
+							  TM_IndexDeleteOp *delstate)
+{
+	elog(INFO, "%s", __func__);
+	return InvalidTransactionId;
+}
+
+/* ----------------------------------------------------------------------------
+ *  Functions for manipulations of physical tuples for dummy_table_am AM.
+ * ----------------------------------------------------------------------------
+ */
+
+static void
+dummy_table_am_tuple_insert(Relation relation, TupleTableSlot *slot,
+					   CommandId cid, int options, BulkInsertState bistate)
+{
+	elog(INFO, "%s", __func__);
+}
+
+static void
+dummy_table_am_tuple_insert_speculative(Relation relation, TupleTableSlot *slot,
+								   CommandId cid, int options,
+								   BulkInsertState bistate,
+								   uint32 specToken)
+{
+	elog(INFO, "%s", __func__);
+}
+
+static void
+dummy_table_am_tuple_complete_speculative(Relation relation, TupleTableSlot *slot,
+									 uint32 spekToken, bool succeeded)
+{
+	elog(INFO, "%s", __func__);
+}
+
+static void
+dummy_table_am_multi_insert(Relation relation, TupleTableSlot **slots,
+					   int ntuples, CommandId cid, int options,
+					   BulkInsertState bistate)
+{
+	elog(INFO, "%s", __func__);
+}
+
+static TM_Result
+dummy_table_am_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
+					   Snapshot snapshot, Snapshot crosscheck, bool wait,
+					   TM_FailureData *tmfd, bool changingPart)
+{
+	elog(INFO, "%s", __func__);
+
+	/* nothing to do, so it is always OK */
+	return TM_Ok;
+}
+
+
+static TM_Result
+dummy_table_am_tuple_update(Relation relation, ItemPointer otid,
+					   TupleTableSlot *slot, CommandId cid,
+					   Snapshot snapshot, Snapshot crosscheck,
+					   bool wait, TM_FailureData *tmfd,
+					   LockTupleMode *lockmode,
+					   TU_UpdateIndexes *update_indexes)
+{
+	elog(INFO, "%s", __func__);
+
+	/* nothing to do, so it is always OK */
+	return TM_Ok;
+}
+
+static TM_Result
+dummy_table_am_tuple_lock(Relation relation, ItemPointer tid, Snapshot snapshot,
+					 TupleTableSlot *slot, CommandId cid, LockTupleMode mode,
+					 LockWaitPolicy wait_policy, uint8 flags,
+					 TM_FailureData *tmfd)
+{
+	elog(INFO, "%s", __func__);
+
+	/* nothing to do, so it is always OK */
+	return TM_Ok;
+}
+
+static void
+dummy_table_am_finish_bulk_insert(Relation relation, int options)
+{
+	elog(INFO, "%s", __func__);
+}
+
+
+/* ------------------------------------------------------------------------
+ * DDL related callbacks for dummy_table_am AM.
+ * ------------------------------------------------------------------------
+ */
+
+static void
+dummy_table_am_relation_set_new_filelocator(Relation rel,
+									   const RelFileLocator *newrnode,
+									   char persistence,
+									   TransactionId *freezeXid,
+									   MultiXactId *minmulti)
+{
+	elog(INFO, "%s", __func__);
+}
+
+static void
+dummy_table_am_relation_nontransactional_truncate(Relation rel)
+{
+	elog(INFO, "%s", __func__);
+}
+
+static void
+dummy_table_am_copy_data(Relation rel, const RelFileLocator *newrnode)
+{
+	elog(INFO, "%s", __func__);
+}
+
+static void
+dummy_table_am_copy_for_cluster(Relation OldTable, Relation NewTable,
+						   Relation OldIndex, bool use_sort,
+						   TransactionId OldestXmin,
+						   TransactionId *xid_cutoff,
+						   MultiXactId *multi_cutoff,
+						   double *num_tuples,
+						   double *tups_vacuumed,
+						   double *tups_recently_dead)
+{
+	elog(INFO, "%s", __func__);
+}
+
+static void
+dummy_table_am_vacuum(Relation onerel, VacuumParams *params,
+				 BufferAccessStrategy bstrategy)
+{
+	elog(INFO, "%s", __func__);
+}
+
+static bool
+dummy_table_am_scan_analyze_next_block(TableScanDesc scan, BlockNumber blockno,
+								  BufferAccessStrategy bstrategy)
+{
+	elog(INFO, "%s", __func__);
+
+	/* no data, so no point to analyze next block */
+	return false;
+}
+
+static bool
+dummy_table_am_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin,
+								  double *liverows, double *deadrows,
+								  TupleTableSlot *slot)
+{
+	elog(INFO, "%s", __func__);
+
+	/* no data, so no point to analyze next tuple */
+	return false;
+}
+
+static double
+dummy_table_am_index_build_range_scan(Relation tableRelation,
+								 Relation indexRelation,
+								 IndexInfo *indexInfo,
+								 bool allow_sync,
+								 bool anyvisible,
+								 bool progress,
+								 BlockNumber start_blockno,
+								 BlockNumber numblocks,
+								 IndexBuildCallback callback,
+								 void *callback_state,
+								 TableScanDesc scan)
+{
+	elog(ERROR, "%s", __func__);
+
+	/* no data, so no tuples */
+	return 0;
+}
+
+static void
+dummy_table_am_index_validate_scan(Relation tableRelation,
+							  Relation indexRelation,
+							  IndexInfo *indexInfo,
+							  Snapshot snapshot,
+							  ValidateIndexState *state)
+{
+	elog(INFO, "%s", __func__);
+}
+
+
+/* ------------------------------------------------------------------------
+ * Miscellaneous callbacks for the dummy_table_am AM
+ * ------------------------------------------------------------------------
+ */
+
+static uint64
+dummy_table_am_relation_size(Relation rel, ForkNumber forkNumber)
+{
+	elog(INFO, "%s", __func__);
+
+	/* there is nothing */
+	return 0;
+}
+
+/*
+ * Check to see whether the table needs a TOAST table.
+ */
+static bool
+dummy_table_am_relation_needs_toast_table(Relation rel)
+{
+	elog(INFO, "%s", __func__);
+
+	/* no data, so no toast table needed */
+	return false;
+}
+
+
+/* ------------------------------------------------------------------------
+ * Planner related callbacks for the dummy_table_am AM
+ * ------------------------------------------------------------------------
+ */
+
+static void
+dummy_table_am_estimate_rel_size(Relation rel, int32 *attr_widths,
+							BlockNumber *pages, double *tuples,
+							double *allvisfrac)
+{
+	/* no data available */
+	if (attr_widths)
+		*attr_widths = 0;
+	if (pages)
+		*pages = 0;
+	if (tuples)
+		*tuples = 0;
+	if (allvisfrac)
+		*allvisfrac = 0;
+
+	elog(INFO, "%s", __func__);
+}
+
+
+/* ------------------------------------------------------------------------
+ * Executor related callbacks for the dummy_table_am AM
+ * ------------------------------------------------------------------------
+ */
+
+static bool
+dummy_table_am_scan_bitmap_next_block(TableScanDesc scan,
+								 TBMIterateResult *tbmres)
+{
+	elog(INFO, "%s", __func__);
+
+	/* no data, so no point to scan next block */
+	return false;
+}
+
+static bool
+dummy_table_am_scan_bitmap_next_tuple(TableScanDesc scan,
+								 TBMIterateResult *tbmres,
+								 TupleTableSlot *slot)
+{
+	elog(INFO, "%s", __func__);
+
+	/* no data, so no point to scan next tuple */
+	return false;
+}
+
+static bool
+dummy_table_am_scan_sample_next_block(TableScanDesc scan,
+								 SampleScanState *scanstate)
+{
+	elog(INFO, "%s", __func__);
+
+	/* no data, so no point to scan next block for sampling */
+	return false;
+}
+
+static bool
+dummy_table_am_scan_sample_next_tuple(TableScanDesc scan,
+								 SampleScanState *scanstate,
+								 TupleTableSlot *slot)
+{
+	elog(INFO, "%s", __func__);
+
+	/* no data, so no point to scan next tuple for sampling */
+	return false;
+}
+
+/* ------------------------------------------------------------------------
+ * Definition of the dummy_table_am table access method.
+ * ------------------------------------------------------------------------
+ */
+
+static const TableAmRoutine dummy_table_am_methods = {
+	.type = T_TableAmRoutine,
+
+	.slot_callbacks = dummy_table_am_slot_callbacks,
+
+	.scan_begin = dummy_table_am_scan_begin,
+	.scan_end = dummy_table_am_scan_end,
+	.scan_rescan = dummy_table_am_scan_rescan,
+	.scan_getnextslot = dummy_table_am_scan_getnextslot,
+
+	/* these are common helper functions */
+	.parallelscan_estimate = table_block_parallelscan_estimate,
+	.parallelscan_initialize = table_block_parallelscan_initialize,
+	.parallelscan_reinitialize = table_block_parallelscan_reinitialize,
+
+	.index_fetch_begin = dummy_table_am_index_fetch_begin,
+	.index_fetch_reset = dummy_table_am_index_fetch_reset,
+	.index_fetch_end = dummy_table_am_index_fetch_end,
+	.index_fetch_tuple = dummy_table_am_index_fetch_tuple,
+
+	.tuple_insert = dummy_table_am_tuple_insert,
+	.tuple_insert_speculative = dummy_table_am_tuple_insert_speculative,
+	.tuple_complete_speculative = dummy_table_am_tuple_complete_speculative,
+	.multi_insert = dummy_table_am_multi_insert,
+	.tuple_delete = dummy_table_am_tuple_delete,
+	.tuple_update = dummy_table_am_tuple_update,
+	.tuple_lock = dummy_table_am_tuple_lock,
+	.finish_bulk_insert = dummy_table_am_finish_bulk_insert,
+
+	.tuple_fetch_row_version = dummy_table_am_fetch_row_version,
+	.tuple_get_latest_tid = dummy_table_am_get_latest_tid,
+	.tuple_tid_valid = dummy_table_am_tuple_tid_valid,
+	.tuple_satisfies_snapshot = dummy_table_am_tuple_satisfies_snapshot,
+	.index_delete_tuples = dummy_table_am_index_delete_tuples,
+
+	.relation_set_new_filelocator = dummy_table_am_relation_set_new_filelocator,
+	.relation_nontransactional_truncate = dummy_table_am_relation_nontransactional_truncate,
+	.relation_copy_data = dummy_table_am_copy_data,
+	.relation_copy_for_cluster = dummy_table_am_copy_for_cluster,
+	.relation_vacuum = dummy_table_am_vacuum,
+	.scan_analyze_next_block = dummy_table_am_scan_analyze_next_block,
+	.scan_analyze_next_tuple = dummy_table_am_scan_analyze_next_tuple,
+	.index_build_range_scan = dummy_table_am_index_build_range_scan,
+	.index_validate_scan = dummy_table_am_index_validate_scan,
+
+	.relation_size = dummy_table_am_relation_size,
+	.relation_needs_toast_table = dummy_table_am_relation_needs_toast_table,
+
+	.relation_estimate_size = dummy_table_am_estimate_rel_size,
+
+	.scan_bitmap_next_block = dummy_table_am_scan_bitmap_next_block,
+	.scan_bitmap_next_tuple = dummy_table_am_scan_bitmap_next_tuple,
+	.scan_sample_next_block = dummy_table_am_scan_sample_next_block,
+	.scan_sample_next_tuple = dummy_table_am_scan_sample_next_tuple
+};
+
+/*
+ * Table AM handler function: returns TableAmRoutine with access method
+ * parameters and callbacks.
+ */
+Datum
+dummy_table_am_handler(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_POINTER(&dummy_table_am_methods);
+}
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 0000000000..08f2f868d4
--- /dev/null
+++ b/src/test/modules/dummy_table_am/dummy_table_am.control
@@ -0,0 +1,5 @@
+# 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/dummy_table_am.out b/src/test/modules/dummy_table_am/expected/dummy_table_am.out
new file mode 100644
index 0000000000..758dc1b9eb
--- /dev/null
+++ b/src/test/modules/dummy_table_am/expected/dummy_table_am.out
@@ -0,0 +1,127 @@
+-- Tests for dummy table access method
+CREATE EXTENSION dummy_table_am;
+CREATE TABLE dummy_table (a int, b text) USING dummy_table_am;
+INFO:  dummy_table_am_relation_set_new_filelocator
+INFO:  dummy_table_am_relation_needs_toast_table
+-- Will error out
+CREATE INDEX dummy_table_idx ON dummy_table (a);
+INFO:  dummy_table_am_estimate_rel_size
+ERROR:  dummy_table_am_index_build_range_scan
+SELECT * FROM dummy_table;
+INFO:  dummy_table_am_estimate_rel_size
+INFO:  dummy_table_am_slot_callbacks
+INFO:  dummy_table_am_scan_begin
+INFO:  dummy_table_am_scan_getnextslot
+INFO:  dummy_table_am_scan_end
+ a | b 
+---+---
+(0 rows)
+
+INSERT INTO dummy_table VALUES (1, 'dummy');
+INFO:  dummy_table_am_slot_callbacks
+INFO:  dummy_table_am_tuple_insert
+SELECT * FROM dummy_table;
+INFO:  dummy_table_am_estimate_rel_size
+INFO:  dummy_table_am_slot_callbacks
+INFO:  dummy_table_am_scan_begin
+INFO:  dummy_table_am_scan_getnextslot
+INFO:  dummy_table_am_scan_end
+ a | b 
+---+---
+(0 rows)
+
+UPDATE dummy_table SET a = 0 WHERE a = 1;
+INFO:  dummy_table_am_estimate_rel_size
+INFO:  dummy_table_am_slot_callbacks
+INFO:  dummy_table_am_scan_begin
+INFO:  dummy_table_am_scan_getnextslot
+INFO:  dummy_table_am_scan_end
+SELECT * FROM dummy_table;
+INFO:  dummy_table_am_estimate_rel_size
+INFO:  dummy_table_am_slot_callbacks
+INFO:  dummy_table_am_scan_begin
+INFO:  dummy_table_am_scan_getnextslot
+INFO:  dummy_table_am_scan_end
+ a | b 
+---+---
+(0 rows)
+
+DELETE FROM dummy_table WHERE a = 1;
+INFO:  dummy_table_am_estimate_rel_size
+INFO:  dummy_table_am_slot_callbacks
+INFO:  dummy_table_am_scan_begin
+INFO:  dummy_table_am_scan_getnextslot
+INFO:  dummy_table_am_scan_end
+SELECT * FROM dummy_table;
+INFO:  dummy_table_am_estimate_rel_size
+INFO:  dummy_table_am_slot_callbacks
+INFO:  dummy_table_am_scan_begin
+INFO:  dummy_table_am_scan_getnextslot
+INFO:  dummy_table_am_scan_end
+ a | b 
+---+---
+(0 rows)
+
+TRUNCATE dummy_table;
+INFO:  dummy_table_am_relation_set_new_filelocator
+VACUUM dummy_table;
+INFO:  dummy_table_am_vacuum
+ANALYZE dummy_table;
+INFO:  dummy_table_am_relation_size
+INFO:  dummy_table_am_relation_size
+INFO:  dummy_table_am_scan_begin
+INFO:  dummy_table_am_slot_callbacks
+INFO:  dummy_table_am_scan_end
+COPY dummy_table TO STDOUT;
+INFO:  dummy_table_am_scan_begin
+INFO:  dummy_table_am_slot_callbacks
+INFO:  dummy_table_am_scan_getnextslot
+INFO:  dummy_table_am_scan_end
+COPY dummy_table (a, b) FROM STDIN;
+INFO:  dummy_table_am_slot_callbacks
+INFO:  dummy_table_am_slot_callbacks
+INFO:  dummy_table_am_multi_insert
+INFO:  dummy_table_am_finish_bulk_insert
+-- ALTER TABLE SET ACCESS METHOD
+ALTER TABLE dummy_table SET ACCESS METHOD heap;
+INFO:  dummy_table_am_relation_needs_toast_table
+INFO:  dummy_table_am_slot_callbacks
+INFO:  dummy_table_am_scan_begin
+INFO:  dummy_table_am_scan_getnextslot
+INFO:  dummy_table_am_scan_end
+CREATE INDEX dummy_table_idx ON dummy_table (a);
+INSERT INTO dummy_table VALUES (1, 'heap');
+SELECT * FROM dummy_table;
+ a |  b   
+---+------
+ 1 | heap
+(1 row)
+
+-- Will error out, the index must be dropped
+ALTER TABLE dummy_table SET ACCESS METHOD dummy_table_am;
+INFO:  dummy_table_am_relation_set_new_filelocator
+INFO:  dummy_table_am_relation_needs_toast_table
+INFO:  dummy_table_am_slot_callbacks
+INFO:  dummy_table_am_tuple_insert
+INFO:  dummy_table_am_finish_bulk_insert
+INFO:  dummy_table_am_estimate_rel_size
+ERROR:  dummy_table_am_index_build_range_scan
+DROP INDEX dummy_table_idx;
+ALTER TABLE dummy_table SET ACCESS METHOD dummy_table_am;
+INFO:  dummy_table_am_relation_set_new_filelocator
+INFO:  dummy_table_am_relation_needs_toast_table
+INFO:  dummy_table_am_slot_callbacks
+INFO:  dummy_table_am_tuple_insert
+INFO:  dummy_table_am_finish_bulk_insert
+SELECT * FROM dummy_table;
+INFO:  dummy_table_am_estimate_rel_size
+INFO:  dummy_table_am_slot_callbacks
+INFO:  dummy_table_am_scan_begin
+INFO:  dummy_table_am_scan_getnextslot
+INFO:  dummy_table_am_scan_end
+ a | b 
+---+---
+(0 rows)
+
+-- Clean up
+DROP TABLE dummy_table;
diff --git a/src/test/modules/dummy_table_am/meson.build b/src/test/modules/dummy_table_am/meson.build
new file mode 100644
index 0000000000..93f9108f29
--- /dev/null
+++ b/src/test/modules/dummy_table_am/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+dummy_table_am_sources = files(
+  'dummy_table_am.c',
+)
+
+if host_system == 'windows'
+  dummy_table_am_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dummy_table_am',
+    '--FILEDESC', 'dummy_table_am - table access method template',])
+endif
+
+dummy_table_am = shared_module('dummy_table_am',
+  dummy_table_am_sources,
+  kwargs: pg_test_mod_args,
+)
+test_install_libs += dummy_table_am
+
+test_install_data += files(
+  'dummy_table_am.control',
+  'dummy_table_am--1.0.sql',
+)
+
+tests += {
+  'name': 'dummy_table_am',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'regress': {
+    'sql': [
+      'dummy_table_am',
+    ],
+  },
+}
diff --git a/src/test/modules/dummy_table_am/sql/dummy_table_am.sql b/src/test/modules/dummy_table_am/sql/dummy_table_am.sql
new file mode 100644
index 0000000000..ae693683ab
--- /dev/null
+++ b/src/test/modules/dummy_table_am/sql/dummy_table_am.sql
@@ -0,0 +1,34 @@
+-- Tests for dummy table access method
+CREATE EXTENSION dummy_table_am;
+CREATE TABLE dummy_table (a int, b text) USING dummy_table_am;
+-- Will error out
+CREATE INDEX dummy_table_idx ON dummy_table (a);
+SELECT * FROM dummy_table;
+INSERT INTO dummy_table VALUES (1, 'dummy');
+SELECT * FROM dummy_table;
+UPDATE dummy_table SET a = 0 WHERE a = 1;
+SELECT * FROM dummy_table;
+DELETE FROM dummy_table WHERE a = 1;
+SELECT * FROM dummy_table;
+TRUNCATE dummy_table;
+VACUUM dummy_table;
+ANALYZE dummy_table;
+COPY dummy_table TO STDOUT;
+COPY dummy_table (a, b) FROM STDIN;
+1	dummy
+\.
+
+-- ALTER TABLE SET ACCESS METHOD
+ALTER TABLE dummy_table SET ACCESS METHOD heap;
+CREATE INDEX dummy_table_idx ON dummy_table (a);
+INSERT INTO dummy_table VALUES (1, 'heap');
+SELECT * FROM dummy_table;
+
+-- Will error out, the index must be dropped
+ALTER TABLE dummy_table SET ACCESS METHOD dummy_table_am;
+DROP INDEX dummy_table_idx;
+ALTER TABLE dummy_table SET ACCESS METHOD dummy_table_am;
+SELECT * FROM dummy_table;
+
+-- Clean up
+DROP TABLE dummy_table;
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 17d369e378..84460f27b4 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -5,6 +5,7 @@ subdir('commit_ts')
 subdir('delay_execution')
 subdir('dummy_index_am')
 subdir('dummy_seclabel')
+subdir('dummy_table_am')
 subdir('ldap_password_func')
 subdir('libpq_pipeline')
 subdir('plsample')
-- 
2.34.1

Reply via email to