From 83a4138f6e3bb7fa80b32b1a7f7b579e327e9489 Mon Sep 17 00:00:00 2001
From: Phil Eaton <phil@eatonphil.com>
Date: Fri, 3 May 2024 12:08:55 -0400
Subject: [PATCH v2] Add boilerplate C code and SQL registration example for
 custom table access methods.

---
 doc/src/sgml/tableam.sgml | 353 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 351 insertions(+), 2 deletions(-)

diff --git a/doc/src/sgml/tableam.sgml b/doc/src/sgml/tableam.sgml
index 4b37f2e5a6..3d91407ae4 100644
--- a/doc/src/sgml/tableam.sgml
+++ b/doc/src/sgml/tableam.sgml
@@ -35,13 +35,362 @@
   argument of type <type>internal</type> and to return the pseudo-type
   <type>table_am_handler</type>.  The argument is a dummy value that simply
   serves to prevent handler functions from being called directly from SQL commands.
+ </para>
+
+ <para>
+  Here is an example of how to register an extension that provides a
+  table access method handler:
+ </para>
+
+<screen>
+CREATE OR REPLACE FUNCTION my_tableam_handler(internal)
+RETURNS table_am_handler AS 'myextension', 'my_tableam_handler'
+LANGUAGE C STRICT;
 
+CREATE ACCESS METHOD myam TYPE TABLE HANDLER my_tableam_handler;
+</screen>
+
+ <para>
   The result of the function must be a pointer to a struct of type
   <structname>TableAmRoutine</structname>, which contains everything that the
   core code needs to know to make use of the table access method. The return
   value needs to be of server lifetime, which is typically achieved by
-  defining it as a <literal>static const</literal> variable in global
-  scope. The <structname>TableAmRoutine</structname> struct, also called the
+  defining it as a <literal>static const</literal> variable in global scope.
+ </para>
+
+ <para>
+  Here is what <filename>myextension.c</filename> with the table
+  access method handler might look like:
+ </para>
+
+<screen>
+#include "postgres.h"
+#include "fmgr.h"
+#include "access/tableam.h"
+#include "access/heapam.h"
+#include "nodes/execnodes.h"
+#include "catalog/index.h"
+#include "commands/vacuum.h"
+#include "utils/builtins.h"
+#include "executor/tuptable.h"
+
+PG_MODULE_MAGIC;
+
+const TableAmRoutine my_am_methods;
+
+static const TupleTableSlotOps* my_am_slot_callbacks(Relation relation)
+{
+        return NULL;
+}
+
+static TableScanDesc my_am_beginscan(Relation relation,
+                                     Snapshot snapshot,
+                                     int nkeys,
+                                     struct ScanKeyData *key,
+                                     ParallelTableScanDesc parallel_scan,
+                                     uint32 flags)
+{
+        return NULL;
+}
+
+static void my_am_rescan(TableScanDesc sscan,
+                         struct ScanKeyData *key,
+                         bool set_params,
+                         bool allow_strat,
+                         bool allow_sync,
+                         bool allow_pagemode) {}
+
+static void my_am_endscan(TableScanDesc sscan) {}
+
+static bool my_am_getnextslot(TableScanDesc sscan,
+                              ScanDirection direction,
+                              TupleTableSlot *slot)
+{
+        return false;
+}
+
+static IndexFetchTableData* my_am_index_fetch_begin(Relation rel)
+{
+        return NULL;
+}
+
+static void my_am_index_fetch_reset(IndexFetchTableData *scan) {}
+
+static void my_am_index_fetch_end(IndexFetchTableData *scan) {}
+
+static bool my_am_index_fetch_tuple(struct IndexFetchTableData *scan,
+                                    ItemPointer tid,
+                                    Snapshot snapshot,
+                                    TupleTableSlot *slot,
+                                    bool *call_again,
+                                    bool *all_dead)
+{
+        return false;
+}
+
+static void my_am_tuple_insert(Relation relation,
+                               TupleTableSlot *slot,
+                               CommandId cid,
+                               int options,
+                               BulkInsertState bistate) {}
+
+static void my_am_tuple_insert_speculative(Relation relation,
+                                           TupleTableSlot *slot,
+                                           CommandId cid,
+                                           int options,
+                                           BulkInsertState bistate,
+                                           uint32 specToken) {}
+
+static void my_am_tuple_complete_speculative(Relation relation,
+                                             TupleTableSlot *slot,
+                                             uint32 specToken,
+                                             bool succeeded) {}
+
+static void my_am_multi_insert(Relation relation,
+                               TupleTableSlot **slots,
+                               int ntuples,
+                               CommandId cid,
+                               int options,
+                               BulkInsertState bistate) {}
+
+static TM_Result my_am_tuple_delete(Relation relation,
+                                    ItemPointer tid,
+                                    CommandId cid,
+                                    Snapshot snapshot,
+                                    Snapshot crosscheck,
+                                    bool wait,
+                                    TM_FailureData *tmfd,
+                                    bool changingPart)
+{
+        return TM_Ok;
+}
+
+static TM_Result my_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)
+{
+        return TM_Ok;
+}
+
+static TM_Result my_am_tuple_lock(Relation relation,
+                                  ItemPointer tid,
+                                  Snapshot snapshot,
+                                  TupleTableSlot *slot,
+                                  CommandId cid,
+                                  LockTupleMode mode,
+                                  LockWaitPolicy wait_policy,
+                                  uint8 flags,
+                                  TM_FailureData *tmfd)
+{
+        return TM_Ok;
+}
+
+static bool my_am_fetch_row_version(Relation relation,
+                                    ItemPointer tid,
+                                    Snapshot snapshot,
+                                    TupleTableSlot *slot)
+{
+        return false;
+}
+
+static void my_am_get_latest_tid(TableScanDesc sscan, ItemPointer tid) {}
+
+static bool my_am_tuple_tid_valid(TableScanDesc scan, ItemPointer tid)
+{
+        return false;
+}
+
+static bool my_am_tuple_satisfies_snapshot(Relation rel,
+                                           TupleTableSlot *slot,
+                                           Snapshot snapshot)
+{
+        return false;
+}
+
+static TransactionId my_am_index_delete_tuples(Relation rel,
+                                               TM_IndexDeleteOp *delstate)
+{
+        return 0;
+}
+
+static void my_am_relation_set_new_filelocator(Relation rel,
+                                               const RelFileLocator *newrlocator,
+                                               char persistence,
+                                               TransactionId *freezeXid,
+                                               MultiXactId *minmulti) {}
+
+static void my_am_relation_nontransactional_truncate(Relation rel) {}
+
+static void my_am_relation_copy_data(Relation rel,
+                                     const RelFileLocator *newrlocator) {}
+
+static void my_am_relation_copy_for_cluster(Relation OldHeap,
+                                            Relation NewHeap,
+                                            Relation OldIndex,
+                                            bool use_sort,
+                                            TransactionId OldestXmin,
+                                            TransactionId *xid_cutoff,
+                                            MultiXactId *multi_cutoff,
+                                            double *num_tuples,
+                                            double *tups_vacuumed,
+                                            double *tups_recently_dead) {}
+
+static void my_am_vacuum_rel(Relation rel,
+                             VacuumParams *params,
+                             BufferAccessStrategy bstrategy) {}
+
+static bool my_am_scan_analyze_next_block(TableScanDesc scan,
+                                          ReadStream *stream)
+{
+        return false;
+}
+
+static bool my_am_scan_analyze_next_tuple(TableScanDesc scan,
+                                          TransactionId OldestXmin,
+                                          double *liverows,
+                                          double *deadrows,
+                                          TupleTableSlot *slot)
+{
+        return false;
+}
+
+static double my_am_index_build_range_scan(Relation heapRelation,
+                                           Relation indexRelation,
+                                           IndexInfo *indexInfo,
+                                           bool allow_sync,
+                                           bool anyvisible,
+                                           bool progress,
+                                           BlockNumber start_blockno,
+                                           BlockNumber numblocks,
+                                           IndexBuildCallback callback,
+                                           void *callback_state,
+                                           TableScanDesc scan)
+{
+        return 0;
+}
+
+static void my_am_index_validate_scan(Relation heapRelation,
+                                      Relation indexRelation,
+                                      IndexInfo *indexInfo,
+                                      Snapshot snapshot,
+                                      ValidateIndexState *state) {}
+
+static bool my_am_relation_needs_toast_table(Relation rel)
+{
+        return false;
+}
+
+static Oid my_am_relation_toast_am(Relation rel)
+{
+        return InvalidOid;
+}
+
+static void my_am_fetch_toast_slice(Relation toastrel,
+                                    Oid valueid,
+                                    int32 attrsize,
+                                    int32 sliceoffset,
+                                    int32 slicelength,
+                                    struct varlena *result) {}
+
+static void my_am_estimate_rel_size(Relation rel,
+                                    int32 *attr_widths,
+                                    BlockNumber *pages,
+                                    double *tuples,
+                                    double *allvisfrac) {}
+
+static bool my_am_scan_sample_next_block(TableScanDesc scan,
+                                         SampleScanState *scanstate)
+{
+        return false;
+}
+
+static bool my_am_scan_sample_next_tuple(TableScanDesc scan,
+                                         SampleScanState *scanstate,
+                                         TupleTableSlot *slot)
+{
+        return false;
+}
+
+const TableAmRoutine my_am_methods = {
+        .type = T_TableAmRoutine,
+
+        /* Required TableAmRoutine methods. */
+
+        .slot_callbacks = my_am_slot_callbacks,
+
+        .scan_begin = my_am_beginscan,
+        .scan_end = my_am_endscan,
+        .scan_rescan = my_am_rescan,
+        .scan_getnextslot = my_am_getnextslot,
+
+        .parallelscan_estimate = table_block_parallelscan_estimate,
+        .parallelscan_initialize = table_block_parallelscan_initialize,
+        .parallelscan_reinitialize = table_block_parallelscan_reinitialize,
+
+        .index_fetch_begin = my_am_index_fetch_begin,
+        .index_fetch_reset = my_am_index_fetch_reset,
+        .index_fetch_end = my_am_index_fetch_end,
+        .index_fetch_tuple = my_am_index_fetch_tuple,
+
+        .tuple_insert = my_am_tuple_insert,
+        .tuple_insert_speculative = my_am_tuple_insert_speculative,
+        .tuple_complete_speculative = my_am_tuple_complete_speculative,
+        .multi_insert = my_am_multi_insert,
+        .tuple_delete = my_am_tuple_delete,
+        .tuple_update = my_am_tuple_update,
+        .tuple_lock = my_am_tuple_lock,
+
+        .tuple_fetch_row_version = my_am_fetch_row_version,
+        .tuple_get_latest_tid = my_am_get_latest_tid,
+        .tuple_tid_valid = my_am_tuple_tid_valid,
+        .tuple_satisfies_snapshot = my_am_tuple_satisfies_snapshot,
+        .index_delete_tuples = my_am_index_delete_tuples,
+
+        .relation_set_new_filelocator = my_am_relation_set_new_filelocator,
+        .relation_nontransactional_truncate = my_am_relation_nontransactional_truncate,
+        .relation_copy_data = my_am_relation_copy_data,
+        .relation_copy_for_cluster = my_am_relation_copy_for_cluster,
+        .relation_vacuum = my_am_vacuum_rel,
+        .scan_analyze_next_block = my_am_scan_analyze_next_block,
+        .scan_analyze_next_tuple = my_am_scan_analyze_next_tuple,
+        .index_build_range_scan = my_am_index_build_range_scan,
+        .index_validate_scan = my_am_index_validate_scan,
+
+        .relation_size = table_block_relation_size,
+        .relation_needs_toast_table = my_am_relation_needs_toast_table,
+        .relation_toast_am = my_am_relation_toast_am,
+        .relation_fetch_toast_slice = my_am_fetch_toast_slice,
+
+        .relation_estimate_size = my_am_estimate_rel_size,
+
+        .scan_sample_next_block = my_am_scan_sample_next_block,
+        .scan_sample_next_tuple = my_am_scan_sample_next_tuple
+
+        /*
+         * Optional TableAmRoutine methods:
+         * .finish_bulk_insert = my_am_finish_bulk_insert,
+         * .scan_set_tidrange = my_am_scan_set_tidrange,
+         * .scan_getnextslot_tidrange = my_am_scan_getnextslot_tidrange,
+         */
+};
+
+PG_FUNCTION_INFO_V1(mem_tableam_handler);
+
+Datum mem_tableam_handler(PG_FUNCTION_ARGS)
+{
+        PG_RETURN_POINTER(&#038;my_am_methods);
+}
+</screen>
+
+ <para>
+  The <structname>TableAmRoutine</structname> struct, also called the
   access method's <firstterm>API struct</firstterm>, defines the behavior of
   the access method using callbacks. These callbacks are pointers to plain C
   functions and are not visible or callable at the SQL level. All the
-- 
2.39.3 (Apple Git-146)

