Add test module for Table Access Method
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
Attachments:
0001-Add-test-module-for-Table-Access-Method.patchtext/x-patch; charset=US-ASCII; name=0001-Add-test-module-for-Table-Access-Method.patchDownload
From 217b84f21ec1cdb0ede271d24b7a5863713db949 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fabr=C3=ADzio=20de=20Royes=20Mello?=
<fabriziomello@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
On Sat, Jun 3, 2023 at 7:42 PM Fabrízio de Royes Mello <
fabriziomello@gmail.com> wrote:
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
Just added some more tests, ran pgindent and also organized a bit some
comments and README.txt.
Regards,
--
Fabrízio de Royes Mello
Attachments:
v2-0001-Add-test-module-for-Table-Access-Method.patchtext/x-patch; charset=US-ASCII; name=v2-0001-Add-test-module-for-Table-Access-Method.patchDownload
From 31d6bf00fbee6c229382db0760ba602e4d41c917 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fabr=C3=ADzio=20de=20Royes=20Mello?=
<fabriziomello@gmail.com>
Date: Sat, 3 Jun 2023 17:23:05 -0400
Subject: [PATCH v2] 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 | 5 +
.../dummy_table_am/dummy_table_am--1.0.sql | 14 +
.../modules/dummy_table_am/dummy_table_am.c | 500 ++++++++++++++++++
.../dummy_table_am/dummy_table_am.control | 5 +
.../expected/dummy_table_am.out | 207 ++++++++
src/test/modules/dummy_table_am/meson.build | 33 ++
.../dummy_table_am/sql/dummy_table_am.sql | 55 ++
src/test/modules/meson.build | 1 +
11 files changed, 844 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..35211554b0
--- /dev/null
+++ b/src/test/modules/dummy_table_am/README
@@ -0,0 +1,5 @@
+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.
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..132f7b18cd
--- /dev/null
+++ b/src/test/modules/dummy_table_am/dummy_table_am.c
@@ -0,0 +1,500 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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..76029a30ae
--- /dev/null
+++ b/src/test/modules/dummy_table_am/expected/dummy_table_am.out
@@ -0,0 +1,207 @@
+-- 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)
+
+INSERT INTO dummy_table VALUES (1, 'dummy'), (2, 'dummy');
+INFO: dummy_table_am_slot_callbacks
+INFO: dummy_table_am_tuple_insert
+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)
+
+-- Update without WHERE
+UPDATE dummy_table SET a = 0, b = NULL;
+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
+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)
+
+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
+VACUUM (FULL) dummy_table;
+INFO: dummy_table_am_relation_set_new_filelocator
+INFO: dummy_table_am_copy_for_cluster
+INFO: dummy_table_am_relation_size
+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)
+
+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
+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)
+
+ALTER TABLE dummy_table ADD COLUMN c BOOLEAN DEFAULT true;
+INFO: dummy_table_am_relation_needs_toast_table
+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 | c
+---+---+---
+(0 rows)
+
+-- 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', false);
+SELECT * FROM dummy_table;
+ a | b | c
+---+------+---
+ 1 | heap | f
+(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 | c
+---+---+---
+(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..f17c169255
--- /dev/null
+++ b/src/test/modules/dummy_table_am/sql/dummy_table_am.sql
@@ -0,0 +1,55 @@
+-- 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;
+
+INSERT INTO dummy_table VALUES (1, 'dummy'), (2, 'dummy');
+SELECT * FROM dummy_table;
+
+UPDATE dummy_table SET a = 0 WHERE a = 1;
+SELECT * FROM dummy_table;
+
+-- Update without WHERE
+UPDATE dummy_table SET a = 0, b = NULL;
+SELECT * FROM dummy_table;
+
+DELETE FROM dummy_table WHERE a = 1;
+SELECT * FROM dummy_table;
+
+TRUNCATE dummy_table;
+SELECT * FROM dummy_table;
+
+VACUUM dummy_table;
+ANALYZE dummy_table;
+VACUUM (FULL) dummy_table;
+SELECT * FROM dummy_table;
+
+COPY dummy_table TO STDOUT;
+COPY dummy_table (a, b) FROM STDIN;
+1 dummy
+\.
+SELECT * FROM dummy_table;
+
+ALTER TABLE dummy_table ADD COLUMN c BOOLEAN DEFAULT true;
+INSERT INTO dummy_table VALUES (1, 'dummy');
+SELECT * FROM dummy_table;
+
+-- 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', false);
+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
On Mon, Jun 5, 2023 at 1:24 PM Fabrízio de Royes Mello <
fabriziomello@gmail.com> wrote:
On Sat, Jun 3, 2023 at 7:42 PM Fabrízio de Royes Mello <
fabriziomello@gmail.com> wrote:
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
Just added some more tests, ran pgindent and also organized a bit some
comments and README.txt.
Rebased version.
--
Fabrízio de Royes Mello
Attachments:
v3-0001-Add-test-module-for-Table-Access-Method.patchtext/x-patch; charset=US-ASCII; name=v3-0001-Add-test-module-for-Table-Access-Method.patchDownload
From 5b6642b520874f4ca7023fc33d2e8e875fb64693 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fabr=C3=ADzio=20de=20Royes=20Mello?=
<fabriziomello@gmail.com>
Date: Sat, 3 Jun 2023 17:23:05 -0400
Subject: [PATCH v3] 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 | 5 +
.../dummy_table_am/dummy_table_am--1.0.sql | 14 +
.../modules/dummy_table_am/dummy_table_am.c | 500 ++++++++++++++++++
.../dummy_table_am/dummy_table_am.control | 5 +
.../expected/dummy_table_am.out | 207 ++++++++
src/test/modules/dummy_table_am/meson.build | 33 ++
.../dummy_table_am/sql/dummy_table_am.sql | 55 ++
src/test/modules/meson.build | 1 +
11 files changed, 844 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 e81873cb5a..cb7a5b970a 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 \
spgist_name_ops \
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..35211554b0
--- /dev/null
+++ b/src/test/modules/dummy_table_am/README
@@ -0,0 +1,5 @@
+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.
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..132f7b18cd
--- /dev/null
+++ b/src/test/modules/dummy_table_am/dummy_table_am.c
@@ -0,0 +1,500 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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..76029a30ae
--- /dev/null
+++ b/src/test/modules/dummy_table_am/expected/dummy_table_am.out
@@ -0,0 +1,207 @@
+-- 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)
+
+INSERT INTO dummy_table VALUES (1, 'dummy'), (2, 'dummy');
+INFO: dummy_table_am_slot_callbacks
+INFO: dummy_table_am_tuple_insert
+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)
+
+-- Update without WHERE
+UPDATE dummy_table SET a = 0, b = NULL;
+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
+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)
+
+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
+VACUUM (FULL) dummy_table;
+INFO: dummy_table_am_relation_set_new_filelocator
+INFO: dummy_table_am_copy_for_cluster
+INFO: dummy_table_am_relation_size
+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)
+
+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
+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)
+
+ALTER TABLE dummy_table ADD COLUMN c BOOLEAN DEFAULT true;
+INFO: dummy_table_am_relation_needs_toast_table
+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 | c
+---+---+---
+(0 rows)
+
+-- 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', false);
+SELECT * FROM dummy_table;
+ a | b | c
+---+------+---
+ 1 | heap | f
+(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 | c
+---+---+---
+(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..f17c169255
--- /dev/null
+++ b/src/test/modules/dummy_table_am/sql/dummy_table_am.sql
@@ -0,0 +1,55 @@
+-- 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;
+
+INSERT INTO dummy_table VALUES (1, 'dummy'), (2, 'dummy');
+SELECT * FROM dummy_table;
+
+UPDATE dummy_table SET a = 0 WHERE a = 1;
+SELECT * FROM dummy_table;
+
+-- Update without WHERE
+UPDATE dummy_table SET a = 0, b = NULL;
+SELECT * FROM dummy_table;
+
+DELETE FROM dummy_table WHERE a = 1;
+SELECT * FROM dummy_table;
+
+TRUNCATE dummy_table;
+SELECT * FROM dummy_table;
+
+VACUUM dummy_table;
+ANALYZE dummy_table;
+VACUUM (FULL) dummy_table;
+SELECT * FROM dummy_table;
+
+COPY dummy_table TO STDOUT;
+COPY dummy_table (a, b) FROM STDIN;
+1 dummy
+\.
+SELECT * FROM dummy_table;
+
+ALTER TABLE dummy_table ADD COLUMN c BOOLEAN DEFAULT true;
+INSERT INTO dummy_table VALUES (1, 'dummy');
+SELECT * FROM dummy_table;
+
+-- 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', false);
+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 fcd643f6f1..bbddef1050 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
On Sat, Jun 03, 2023 at 07:42:36PM -0400, Fabrízio de Royes Mello wrote:
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
dummy_index_am has included from the start additional coverage for the
various internal add_*_reloption routines, that were never covered in
the core tree. Except if I am missing something, I am not seeing some
of the extra usefulness for the patch you've sent here.
--
Michael
On Thu, 28 Sept 2023 at 10:23, Michael Paquier <michael@paquier.xyz> wrote:
On Sat, Jun 03, 2023 at 07:42:36PM -0400, Fabrízio de Royes Mello wrote:
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_amdummy_index_am has included from the start additional coverage for the
various internal add_*_reloption routines, that were never covered in
the core tree. Except if I am missing something, I am not seeing some
of the extra usefulness for the patch you've sent here.
I have changed the status of commitfest entry to "Returned with
Feedback" as Michael's comments have not yet been resolved. Please
handle the comments and update the commitfest entry accordingly.
Regards,
Vignesh
On Thu, 28 Sept 2023 at 03:08, Michael Paquier <michael@paquier.xyz> wrote:
dummy_index_am has included from the start additional coverage for the
various internal add_*_reloption routines, that were never covered in
the core tree. Except if I am missing something, I am not seeing some
of the extra usefulness for the patch you've sent here.
When trying to implement a table access method in the past I remember
very well that I was having a really hard time finding an example of
one. I remember seeing the dummy_index_am module and being quite
disappointed that there wasn't a similar one for table access methods.
I believe that I eventually found blackhole_am, but it took me quite a
bit of mailing list spelunking to get there. So I think purely for
documentation purposes this addition would already be useful.
Hi,
When trying to implement a table access method in the past I remember
very well that I was having a really hard time finding an example of
one.
To be fair, Postgres uses TAM internally, so there is at least one
complete and up-to-date real-life example. Learning curve for TAMs is
indeed steep, and I wonder if we could do a better job in this respect
e.g. by providing a simpler example. This being said, I know several
people who learned TAM successfully (so far only for R&D tasks) which
indicates that its difficulty is adequate.
--
Best regards,
Aleksander Alekseev
On Mon, 15 Jan 2024 at 14:26, Aleksander Alekseev
<aleksander@timescale.com> wrote:
To be fair, Postgres uses TAM internally, so there is at least one
complete and up-to-date real-life example.
Sure, but that one is quite hard to follow if you don't already know
lots of details of the heap storage. At least for me, having a minimal
example was extremely helpful and it made for a great code skeleton to
start from.
On Mon, Jan 15, 2024 at 03:40:30PM +0100, Jelte Fennema-Nio wrote:
On Mon, 15 Jan 2024 at 14:26, Aleksander Alekseev
<aleksander@timescale.com> wrote:To be fair, Postgres uses TAM internally, so there is at least one
complete and up-to-date real-life example.Sure, but that one is quite hard to follow if you don't already know
lots of details of the heap storage. At least for me, having a minimal
example was extremely helpful and it made for a great code skeleton to
start from.
Hmm. I'd rather have it do something useful in terms of test coverage
rather than being just an empty skull.
How about adding the same kind of coverage as dummy_index_am with a
couple of reloptions then? That can serve as a point of reference
when a table AM needs a few custom options. A second idea would be to
show how to use toast relations when implementing your new AM, where a
toast table could be created even in cases where we did not want one
with heap, when it comes to size limitations with char and/or varchar,
and that makes for a simpler needs_toast_table callback.
--
Michaxel
On Tue, Jan 16, 2024 at 10:28 AM Michael Paquier <michael@paquier.xyz> wrote:
Hmm. I'd rather have it do something useful in terms of test coverage
rather than being just an empty skull.How about adding the same kind of coverage as dummy_index_am with a
couple of reloptions then? That can serve as a point of reference
when a table AM needs a few custom options. A second idea would be to
show how to use toast relations when implementing your new AM, where a
toast table could be created even in cases where we did not want one
with heap, when it comes to size limitations with char and/or varchar,
and that makes for a simpler needs_toast_table callback.
I think a test module for a table AM will really help developers. Just
to add to the above list - how about the table AM implementing a
simple in-memory (columnar if possible) database storing tables
in-memory and subsequently providing readers with the access to the
tables?
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
On Tue, 16 Jan 2024 at 13:15, Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> wrote:
On Tue, Jan 16, 2024 at 10:28 AM Michael Paquier <michael@paquier.xyz> wrote:
Hmm. I'd rather have it do something useful in terms of test coverage
rather than being just an empty skull.How about adding the same kind of coverage as dummy_index_am with a
couple of reloptions then? That can serve as a point of reference
when a table AM needs a few custom options. A second idea would be to
show how to use toast relations when implementing your new AM, where a
toast table could be created even in cases where we did not want one
with heap, when it comes to size limitations with char and/or varchar,
and that makes for a simpler needs_toast_table callback.I think a test module for a table AM will really help developers. Just
to add to the above list - how about the table AM implementing a
simple in-memory (columnar if possible) database storing tables
in-memory and subsequently providing readers with the access to the
tables?
That's a good idea.
Hi,
I think a test module for a table AM will really help developers. Just
to add to the above list - how about the table AM implementing a
simple in-memory (columnar if possible) database storing tables
in-memory and subsequently providing readers with the access to the
tables?That's a good idea.
Personally I would be careful with this idea.
Practice shows that when you show the first incomplete, limited and
buggy PoC it ends up being in the production environment the next day
:) In other words sooner or later there will be users demanding a full
in-memory columnar storage support from Postgres. I believe it would
be a problem. Last time I checked TAM was not extremely good for
implementing proper columnar storages, and there are lots of open
questions when it comes to in-memory tables (e.g. what to do with
foreign keys, inherited tables, etc).
All in all I don't think we should provide something that can look /
be interpreted as first-class alternative storage but in fact is not.
How about adding the same kind of coverage as dummy_index_am with a
couple of reloptions then? That can serve as a point of reference
when a table AM needs a few custom options. A second idea would be to
show how to use toast relations when implementing your new AM, where a
toast table could be created even in cases where we did not want one
with heap, when it comes to size limitations with char and/or varchar,
and that makes for a simpler needs_toast_table callback.
Good ideas. Additionally we could provide a proxy TAM for a heap TAM
which does nothing but logging used TAM methods, its arguments and
return values. This would be a good example and also potentially can
be used as a debugging tool.
--
Best regards,
Aleksander Alekseev
Hi all,
On Tue, Jan 16, 2024 at 10:40 AM Aleksander Alekseev <
aleksander@timescale.com> wrote:
Hi,
I think a test module for a table AM will really help developers. Just
to add to the above list - how about the table AM implementing a
simple in-memory (columnar if possible) database storing tables
in-memory and subsequently providing readers with the access to the
tables?That's a good idea.
Personally I would be careful with this idea.
Practice shows that when you show the first incomplete, limited and
buggy PoC it ends up being in the production environment the next day
:) In other words sooner or later there will be users demanding a full
in-memory columnar storage support from Postgres. I believe it would
be a problem. Last time I checked TAM was not extremely good for
implementing proper columnar storages, and there are lots of open
questions when it comes to in-memory tables (e.g. what to do with
foreign keys, inherited tables, etc).All in all I don't think we should provide something that can look /
be interpreted as first-class alternative storage but in fact is not.
I tossed together a table access method for in-memory storage in column
format for experimental purposes over the holidays (I actually have a
row-based one as well, but that is in no shape to share at this point).
It's available under https://github.com/mkindahl/pg_arrow. The intention
was mostly to have something simple to play and experiment with. It is
loosely based on the Apache Arrow Columnar format, but the normal data
structures are not suitable for storing in shared memory so I have tweaked
it a little.
How about adding the same kind of coverage as dummy_index_am with a
couple of reloptions then? That can serve as a point of reference
when a table AM needs a few custom options. A second idea would be to
show how to use toast relations when implementing your new AM, where a
toast table could be created even in cases where we did not want one
with heap, when it comes to size limitations with char and/or varchar,
and that makes for a simpler needs_toast_table callback.Good ideas. Additionally we could provide a proxy TAM for a heap TAM
which does nothing but logging used TAM methods, its arguments and
return values. This would be a good example and also potentially can
be used as a debugging tool.
We wrote a table access method for experimenting with and to be able to
trace what happens while executing various statements. It is available
under https://github.com/timescale/pg_traceam for anybody who is interested.
Best wishes,
Mats Kindahl
Show quoted text
--
Best regards,
Aleksander Alekseev
On Tue, Jan 16, 2024 at 6:15 AM Bharath Rupireddy <
bharath.rupireddyforpostgres@gmail.com> wrote:
On Tue, Jan 16, 2024 at 10:28 AM Michael Paquier <michael@paquier.xyz>
wrote:Hmm. I'd rather have it do something useful in terms of test coverage
rather than being just an empty skull.How about adding the same kind of coverage as dummy_index_am with a
couple of reloptions then? That can serve as a point of reference
when a table AM needs a few custom options. A second idea would be to
show how to use toast relations when implementing your new AM, where a
toast table could be created even in cases where we did not want one
with heap, when it comes to size limitations with char and/or varchar,
and that makes for a simpler needs_toast_table callback.I think a test module for a table AM will really help developers. Just
to add to the above list - how about the table AM implementing a
simple in-memory (columnar if possible) database storing tables
in-memory and subsequently providing readers with the access to the
tables?
Hi,
One idea I wanted to implement is a table access method that you can use to
test the interface, something like a "mock TAM" where you can
programmatically decide on the responses to unit-test the API. I was
thinking that you could implement a framework that allows you to implement
the TAM in some scripting language like Perl, Python, or (horrors) Tcl for
easy prototyping.
Best wishes,
Mats Kindahl
Show quoted text
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com