From 0611ef60b39907a3bd405ece990d661d4d63900d Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 15 Feb 2021 16:21:01 +0900
Subject: [PATCH] Add no_storage, fallback table AM for relations without
 storage

---
 src/include/access/tableam.h                  |   6 +-
 src/include/catalog/pg_am.dat                 |   4 +
 src/include/catalog/pg_proc.dat               |   4 +
 src/backend/access/Makefile                   |   5 +-
 src/backend/access/no_storage/Makefile        |  18 +
 src/backend/access/no_storage/README          |   9 +
 .../access/no_storage/no_storage_handler.c    | 518 ++++++++++++++++++
 src/backend/access/table/tableam.c            |  33 --
 src/backend/utils/cache/relcache.c            |  37 +-
 src/test/regress/expected/create_am.out       |  11 +-
 src/test/regress/expected/psql.out            |  96 ++--
 11 files changed, 639 insertions(+), 102 deletions(-)
 create mode 100644 src/backend/access/no_storage/Makefile
 create mode 100644 src/backend/access/no_storage/README
 create mode 100644 src/backend/access/no_storage/no_storage_handler.c

diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 33bffb6815..999e05bb89 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -832,7 +832,11 @@ typedef struct TableAmRoutine
  * for the relation.  Works for tables, views, foreign tables and partitioned
  * tables.
  */
-extern const TupleTableSlotOps *table_slot_callbacks(Relation rel);
+static inline const TupleTableSlotOps *
+table_slot_callbacks(Relation rel)
+{
+	return rel->rd_tableam->slot_callbacks(rel);
+}
 
 /*
  * Returns slot using the callbacks returned by table_slot_callbacks(), and
diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat
index 6082f0e6a8..e0e32a681f 100644
--- a/src/include/catalog/pg_am.dat
+++ b/src/include/catalog/pg_am.dat
@@ -15,6 +15,10 @@
 { oid => '2', oid_symbol => 'HEAP_TABLE_AM_OID',
   descr => 'heap table access method',
   amname => 'heap', amhandler => 'heap_tableam_handler', amtype => 't' },
+{ oid => '8381', oid_symbol => 'NO_STORAGE_TABLE_AM_OID',
+  descr => 'no_storage table access method',
+  amname => 'no_storage', amhandler => 'no_storage_tableam_handler',
+  amtype => 't' },
 { oid => '403', oid_symbol => 'BTREE_AM_OID',
   descr => 'b-tree index access method',
   amname => 'btree', amhandler => 'bthandler', amtype => 'i' },
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..5a796324a5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -905,6 +905,10 @@
   proname => 'heap_tableam_handler', provolatile => 'v',
   prorettype => 'table_am_handler', proargtypes => 'internal',
   prosrc => 'heap_tableam_handler' },
+{ oid => '8382', descr => 'no_storage access method handler',
+  proname => 'no_storage_tableam_handler', provolatile => 'v',
+  prorettype => 'table_am_handler', proargtypes => 'internal',
+  prosrc => 'no_storage_tableam_handler' },
 
 # Index access method handlers
 { oid => '330', descr => 'btree index access method handler',
diff --git a/src/backend/access/Makefile b/src/backend/access/Makefile
index 0880e0a8bb..29d3937ada 100644
--- a/src/backend/access/Makefile
+++ b/src/backend/access/Makefile
@@ -8,7 +8,8 @@ subdir = src/backend/access
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-SUBDIRS	    = brin common gin gist hash heap index nbtree rmgrdesc spgist \
-			  table tablesample transam
+SUBDIRS	    = brin common gin gist hash heap index nbtree no_storage \
+	rmgrdesc spgist \
+	table tablesample transam
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/no_storage/Makefile b/src/backend/access/no_storage/Makefile
new file mode 100644
index 0000000000..8d1227b428
--- /dev/null
+++ b/src/backend/access/no_storage/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for access/no_storage
+#
+# IDENTIFICATION
+#    src/backend/access/no_storage/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/access/no_storage
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+	no_storage_handler.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/no_storage/README b/src/backend/access/no_storage/README
new file mode 100644
index 0000000000..cb1d97fa0e
--- /dev/null
+++ b/src/backend/access/no_storage/README
@@ -0,0 +1,9 @@
+src/backend/access/no_storage/README
+
+No-Storage Table Access method
+==============================
+
+no_storage is a table access method used as a default fallback for relation
+types that have no physical storage, issuing an error if there is an attempt
+to access this layer of the system.  This prevents rd_tableam to be NULL for
+a relation cache entry.
diff --git a/src/backend/access/no_storage/no_storage_handler.c b/src/backend/access/no_storage/no_storage_handler.c
new file mode 100644
index 0000000000..00eee46b2d
--- /dev/null
+++ b/src/backend/access/no_storage/no_storage_handler.c
@@ -0,0 +1,518 @@
+/*-------------------------------------------------------------------------
+ *
+ * no_storage_handler.c
+ *	  no_storage table access method code
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/no_storage/no_storage_handler.c
+ *
+ *
+ * NOTES
+ *	  This file introduces the table access method no_storage, that is
+ *	  used as a fallback implementation for relation types without
+ *	  storage.  Most of the callbacks defined in this file would lead
+ *	  to failures for operations not authorized on such relations.
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "miscadmin.h"
+
+#include "access/tableam.h"
+#include "access/heapam.h"
+#include "access/amapi.h"
+#include "catalog/index.h"
+#include "commands/vacuum.h"
+#include "executor/tuptable.h"
+#include "utils/fmgrprotos.h"
+
+static const TableAmRoutine no_storage_methods;
+
+
+/* ------------------------------------------------------------------------
+ * Slot related callbacks for no_storage AM
+ * ------------------------------------------------------------------------
+ */
+
+static const TupleTableSlotOps *
+no_storage_slot_callbacks(Relation relation)
+{
+	if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		/*
+		 * Historically FDWs expect to store heap tuples in slots. Continue
+		 * handing them one, to make it less painful to adapt FDWs to new
+		 * versions. The cost of a heap slot over a virtual slot is pretty
+		 * small.
+		 */
+		return &TTSOpsHeapTuple;
+	}
+
+	/*
+	 * These need to be supported, as some parts of the code (like COPY) need
+	 * to create slots for such relations too. It seems better to centralize
+	 * the knowledge that a heap slot is the right thing in that case here.
+	 */
+	if (relation->rd_rel->relkind != RELKIND_VIEW &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		elog(ERROR, "no_storage_slot_callbacks() failed on relation \"%s\"",
+			 RelationGetRelationName(relation));
+	return &TTSOpsVirtual;
+}
+
+/* ------------------------------------------------------------------------
+ * Table Scan Callbacks for no_storage AM
+ * ------------------------------------------------------------------------
+ */
+
+static TableScanDesc
+no_storage_scan_begin(Relation relation, Snapshot snapshot,
+					  int nkeys, ScanKey key,
+					  ParallelTableScanDesc parallel_scan,
+					  uint32 flags)
+{
+	elog(ERROR, "no_storage_scan_begin() failed on relation \"%s\"",
+		 RelationGetRelationName(relation));
+	return NULL;
+}
+
+static void
+no_storage_scan_end(TableScanDesc sscan)
+{
+	elog(ERROR, "no_storage_scan_end() failed");
+}
+
+static void
+no_storage_scan_rescan(TableScanDesc sscan, ScanKey key, bool set_params,
+					   bool allow_strat, bool allow_sync, bool allow_pagemode)
+{
+	elog(ERROR, "no_storage_scan_rescan() failed");
+}
+
+static bool
+no_storage_scan_getnextslot(TableScanDesc sscan, ScanDirection direction,
+							TupleTableSlot *slot)
+{
+	elog(ERROR, "no_storage_scan_getnextslot() failed");
+	return false;				/* keep compiler quiet */
+}
+
+/* ------------------------------------------------------------------------
+ * Index Scan Callbacks for no_storage AM
+ * ------------------------------------------------------------------------
+ */
+
+static IndexFetchTableData *
+no_storage_index_fetch_begin(Relation rel)
+{
+	elog(ERROR, "no_storage_index_fetch_begin() failed on relation \"%s\"",
+		 RelationGetRelationName(rel));
+	return NULL;
+}
+
+static void
+no_storage_index_fetch_reset(IndexFetchTableData *scan)
+{
+	elog(ERROR, "no_storage_index_fetch_reset() failed");
+}
+
+static void
+no_storage_index_fetch_end(IndexFetchTableData *scan)
+{
+	elog(ERROR, "no_storage_index_fetch_end() failed");
+}
+
+static bool
+no_storage_index_fetch_tuple(struct IndexFetchTableData *scan,
+							 ItemPointer tid,
+							 Snapshot snapshot,
+							 TupleTableSlot *slot,
+							 bool *call_again, bool *all_dead)
+{
+	elog(ERROR, "no_storage_index_fetch_tuple() failed");
+	return 0;
+}
+
+
+/* ------------------------------------------------------------------------
+ * Callbacks for non-modifying operations on individual tuples for
+ * no_storage AM.
+ * ------------------------------------------------------------------------
+ */
+
+static bool
+no_storage_fetch_row_version(Relation relation,
+							 ItemPointer tid,
+							 Snapshot snapshot,
+							 TupleTableSlot *slot)
+{
+	elog(ERROR, "no_storage_fetch_row_version() failed");
+	return false;
+}
+
+static void
+no_storage_get_latest_tid(TableScanDesc sscan,
+						  ItemPointer tid)
+{
+	elog(ERROR, "no_storage_get_latest_tid() failed");
+}
+
+static bool
+no_storage_tuple_tid_valid(TableScanDesc scan, ItemPointer tid)
+{
+	elog(ERROR, "no_storage_tuple_tid_valid() failed");
+	return false;
+}
+
+static bool
+no_storage_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot,
+									Snapshot snapshot)
+{
+	elog(ERROR, "no_storage_tuple_satisfies_snapshot() failed on relation \"%s\"",
+		 RelationGetRelationName(rel));
+	return false;
+}
+
+static TransactionId
+no_storage_index_delete_tuples(Relation rel,
+							   TM_IndexDeleteOp * delstate)
+{
+	elog(ERROR, "no_storage_index_delete_tuples() failed on relation \"%s\"",
+		 RelationGetRelationName(rel));
+	return InvalidTransactionId;
+}
+
+/* ----------------------------------------------------------------------------
+ *  Functions for manipulations of physical tuples for no_storage AM.
+ * ----------------------------------------------------------------------------
+ */
+
+static void
+no_storage_tuple_insert(Relation relation, TupleTableSlot *slot,
+						CommandId cid, int options, BulkInsertState bistate)
+{
+	elog(ERROR, "no_storage_tuple_insert() failed on relation \"%s\"",
+		 RelationGetRelationName(relation));
+}
+
+static void
+no_storage_tuple_insert_speculative(Relation relation, TupleTableSlot *slot,
+									CommandId cid, int options,
+									BulkInsertState bistate,
+									uint32 specToken)
+{
+	elog(ERROR, "no_storage_tuple_insert_speculative() failed on relation \"%s\"",
+		 RelationGetRelationName(relation));
+}
+
+static void
+no_storage_tuple_complete_speculative(Relation relation, TupleTableSlot *slot,
+									  uint32 spekToken, bool succeeded)
+{
+	elog(ERROR, "no_storage_tuple_complete_speculative() failed on relation \"%s\"",
+		 RelationGetRelationName(relation));
+}
+
+static void
+no_storage_multi_insert(Relation relation, TupleTableSlot **slots,
+						int ntuples, CommandId cid, int options,
+						BulkInsertState bistate)
+{
+	elog(ERROR, "no_storage_multi_insert() failed on relation \"%s\"",
+		 RelationGetRelationName(relation));
+}
+
+static TM_Result
+no_storage_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
+						Snapshot snapshot, Snapshot crosscheck, bool wait,
+						TM_FailureData *tmfd, bool changingPart)
+{
+	elog(ERROR, "no_storage_tuple_delete() failed on relation \"%s\"",
+		 RelationGetRelationName(relation));
+	return TM_Ok;
+}
+
+
+static TM_Result
+no_storage_tuple_update(Relation relation, ItemPointer otid,
+						TupleTableSlot *slot, CommandId cid,
+						Snapshot snapshot, Snapshot crosscheck,
+						bool wait, TM_FailureData *tmfd,
+						LockTupleMode *lockmode, bool *update_indexes)
+{
+	elog(ERROR, "no_storage_tuple_update() failed on relation \"%s\"",
+		 RelationGetRelationName(relation));
+	return TM_Ok;
+}
+
+static TM_Result
+no_storage_tuple_lock(Relation relation, ItemPointer tid, Snapshot snapshot,
+					  TupleTableSlot *slot, CommandId cid, LockTupleMode mode,
+					  LockWaitPolicy wait_policy, uint8 flags,
+					  TM_FailureData *tmfd)
+{
+	elog(ERROR, "no_storage_tuple_lock() failed on relation \"%s\"",
+		 RelationGetRelationName(relation));
+	return TM_Ok;
+}
+
+static void
+no_storage_finish_bulk_insert(Relation relation, int options)
+{
+	elog(ERROR, "no_storage_finish_bulk_insert() failed on relation \"%s\"",
+		 RelationGetRelationName(relation));
+}
+
+
+/* ------------------------------------------------------------------------
+ * DDL related callbacks for no_storage AM.
+ * ------------------------------------------------------------------------
+ */
+
+static void
+no_storage_relation_set_new_filenode(Relation rel,
+									 const RelFileNode *newrnode,
+									 char persistence,
+									 TransactionId *freezeXid,
+									 MultiXactId *minmulti)
+{
+	elog(ERROR, "no_storage_relation_set_new_filenode() failed on relation \"%s\"",
+		 RelationGetRelationName(rel));
+}
+
+static void
+no_storage_relation_nontransactional_truncate(Relation rel)
+{
+	elog(ERROR, "no_storage_relation_nontransactional_truncate() failed on relation \"%s\"",
+		 RelationGetRelationName(rel));
+}
+
+static void
+no_storage_copy_data(Relation rel, const RelFileNode *newrnode)
+{
+	elog(ERROR, "no_storage_copy_data() failed on relation \"%s\"",
+		 RelationGetRelationName(rel));
+}
+
+static void
+no_storage_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(ERROR, "no_storage_copy_for_cluster() failed on relation \"%s\"",
+		 RelationGetRelationName(OldTable));
+}
+
+static void
+no_storage_vacuum(Relation onerel, VacuumParams *params,
+				  BufferAccessStrategy bstrategy)
+{
+	elog(ERROR, "no_storage_vacuum() failed on relation \"%s\"",
+		 RelationGetRelationName(onerel));
+}
+
+static bool
+no_storage_scan_analyze_next_block(TableScanDesc scan, BlockNumber blockno,
+								   BufferAccessStrategy bstrategy)
+{
+	elog(ERROR, "no_storage_scan_analyze_next_block() failed");
+	return false;
+}
+
+static bool
+no_storage_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin,
+								   double *liverows, double *deadrows,
+								   TupleTableSlot *slot)
+{
+	elog(ERROR, "no_storage_scan_analyze_next_tuple() failed");
+	return false;
+}
+
+static double
+no_storage_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, "no_storage_index_build_range_scan() failed on relation \"%s\"",
+		 RelationGetRelationName(tableRelation));
+	return 0;
+}
+
+static void
+no_storage_index_validate_scan(Relation tableRelation,
+							   Relation indexRelation,
+							   IndexInfo *indexInfo,
+							   Snapshot snapshot,
+							   ValidateIndexState *state)
+{
+	elog(ERROR, "no_storage_index_validate_scan() failed on relation \"%s\"",
+		 RelationGetRelationName(tableRelation));
+}
+
+
+/* ------------------------------------------------------------------------
+ * Miscellaneous callbacks for the no_storage AM
+ * ------------------------------------------------------------------------
+ */
+
+static uint64
+no_storage_relation_size(Relation rel, ForkNumber forkNumber)
+{
+	elog(ERROR, "no_storage_relation_size() failed on relation \"%s\"",
+		 RelationGetRelationName(rel));
+	return 0;
+}
+
+/*
+ * Check to see whether the table needs a TOAST table.
+ */
+static bool
+no_storage_relation_needs_toast_table(Relation rel)
+{
+	elog(ERROR, "no_storage_relation_needs_toast_table() failed on relation \"%s\"",
+		 RelationGetRelationName(rel));
+	return false;
+}
+
+
+/* ------------------------------------------------------------------------
+ * Planner related callbacks for the no_storage AM
+ * ------------------------------------------------------------------------
+ */
+
+static void
+no_storage_estimate_rel_size(Relation rel, int32 *attr_widths,
+							 BlockNumber *pages, double *tuples,
+							 double *allvisfrac)
+{
+	elog(ERROR, "no_storage_estimate_rel_size() failed on relation \"%s\"",
+		 RelationGetRelationName(rel));
+}
+
+
+/* ------------------------------------------------------------------------
+ * Executor related callbacks for the no_storage AM
+ * ------------------------------------------------------------------------
+ */
+
+static bool
+no_storage_scan_bitmap_next_block(TableScanDesc scan,
+								  TBMIterateResult *tbmres)
+{
+	elog(ERROR, "no_storage_scan_bitmap_next_block() failed");
+	return false;
+}
+
+static bool
+no_storage_scan_bitmap_next_tuple(TableScanDesc scan,
+								  TBMIterateResult *tbmres,
+								  TupleTableSlot *slot)
+{
+	elog(ERROR, "no_storage_scan_bitmap_next_tuple() failed");
+	return false;
+}
+
+static bool
+no_storage_scan_sample_next_block(TableScanDesc scan,
+								  SampleScanState *scanstate)
+{
+	elog(ERROR, "no_storage_scan_sample_next_block() failed");
+	return false;
+}
+
+static bool
+no_storage_scan_sample_next_tuple(TableScanDesc scan,
+								  SampleScanState *scanstate,
+								  TupleTableSlot *slot)
+{
+	elog(ERROR, "no_storage_scan_sample_next_tuple() failed");
+	return false;
+}
+
+
+/* ------------------------------------------------------------------------
+ * Definition of the no_storage table access method.
+ * ------------------------------------------------------------------------
+ */
+
+static const TableAmRoutine no_storage_methods = {
+	.type = T_TableAmRoutine,
+
+	.slot_callbacks = no_storage_slot_callbacks,
+
+	.scan_begin = no_storage_scan_begin,
+	.scan_end = no_storage_scan_end,
+	.scan_rescan = no_storage_scan_rescan,
+	.scan_getnextslot = no_storage_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 = no_storage_index_fetch_begin,
+	.index_fetch_reset = no_storage_index_fetch_reset,
+	.index_fetch_end = no_storage_index_fetch_end,
+	.index_fetch_tuple = no_storage_index_fetch_tuple,
+
+	.tuple_insert = no_storage_tuple_insert,
+	.tuple_insert_speculative = no_storage_tuple_insert_speculative,
+	.tuple_complete_speculative = no_storage_tuple_complete_speculative,
+	.multi_insert = no_storage_multi_insert,
+	.tuple_delete = no_storage_tuple_delete,
+	.tuple_update = no_storage_tuple_update,
+	.tuple_lock = no_storage_tuple_lock,
+	.finish_bulk_insert = no_storage_finish_bulk_insert,
+
+	.tuple_fetch_row_version = no_storage_fetch_row_version,
+	.tuple_get_latest_tid = no_storage_get_latest_tid,
+	.tuple_tid_valid = no_storage_tuple_tid_valid,
+	.tuple_satisfies_snapshot = no_storage_tuple_satisfies_snapshot,
+	.index_delete_tuples = no_storage_index_delete_tuples,
+
+	.relation_set_new_filenode = no_storage_relation_set_new_filenode,
+	.relation_nontransactional_truncate = no_storage_relation_nontransactional_truncate,
+	.relation_copy_data = no_storage_copy_data,
+	.relation_copy_for_cluster = no_storage_copy_for_cluster,
+	.relation_vacuum = no_storage_vacuum,
+	.scan_analyze_next_block = no_storage_scan_analyze_next_block,
+	.scan_analyze_next_tuple = no_storage_scan_analyze_next_tuple,
+	.index_build_range_scan = no_storage_index_build_range_scan,
+	.index_validate_scan = no_storage_index_validate_scan,
+
+	.relation_size = no_storage_relation_size,
+	.relation_needs_toast_table = no_storage_relation_needs_toast_table,
+
+	.relation_estimate_size = no_storage_estimate_rel_size,
+
+	.scan_bitmap_next_block = no_storage_scan_bitmap_next_block,
+	.scan_bitmap_next_tuple = no_storage_scan_bitmap_next_tuple,
+	.scan_sample_next_block = no_storage_scan_sample_next_block,
+	.scan_sample_next_tuple = no_storage_scan_sample_next_tuple
+};
+
+Datum
+no_storage_tableam_handler(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_POINTER(&no_storage_methods);
+}
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index 5ea5bdd810..5e2660b0e6 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -54,39 +54,6 @@ bool		synchronize_seqscans = true;
  * ----------------------------------------------------------------------------
  */
 
-const TupleTableSlotOps *
-table_slot_callbacks(Relation relation)
-{
-	const TupleTableSlotOps *tts_cb;
-
-	if (relation->rd_tableam)
-		tts_cb = relation->rd_tableam->slot_callbacks(relation);
-	else if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
-	{
-		/*
-		 * Historically FDWs expect to store heap tuples in slots. Continue
-		 * handing them one, to make it less painful to adapt FDWs to new
-		 * versions. The cost of a heap slot over a virtual slot is pretty
-		 * small.
-		 */
-		tts_cb = &TTSOpsHeapTuple;
-	}
-	else
-	{
-		/*
-		 * These need to be supported, as some parts of the code (like COPY)
-		 * need to create slots for such relations too. It seems better to
-		 * centralize the knowledge that a heap slot is the right thing in
-		 * that case here.
-		 */
-		Assert(relation->rd_rel->relkind == RELKIND_VIEW ||
-			   relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
-		tts_cb = &TTSOpsVirtual;
-	}
-
-	return tts_cb;
-}
-
 TupleTableSlot *
 table_slot_create(Relation relation, List **reglist)
 {
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 7ef510cd01..095362b489 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1214,6 +1214,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_FOREIGN_TABLE:
 		case RELKIND_PARTITIONED_TABLE:
 			Assert(relation->rd_rel->relam == InvalidOid);
+			RelationInitTableAccessMethod(relation);
 			break;
 	}
 
@@ -1767,7 +1768,23 @@ RelationInitTableAccessMethod(Relation relation)
 	HeapTuple	tuple;
 	Form_pg_am	aform;
 
-	if (relation->rd_rel->relkind == RELKIND_SEQUENCE)
+	/* nothing to do for indexes */
+	if (relation->rd_rel->relkind == RELKIND_INDEX ||
+		relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
+		return;
+
+	if (relation->rd_rel->relkind == RELKIND_VIEW ||
+		relation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE ||
+		relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+		relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		/*
+		 * Relations without storage fall back to their specific to avoid a
+		 * NULL rd_amhandler.
+		 */
+		relation->rd_amhandler = F_NO_STORAGE_TABLEAM_HANDLER;
+	}
+	else if (relation->rd_rel->relkind == RELKIND_SEQUENCE)
 	{
 		/*
 		 * Sequences are currently accessed like heap tables, but it doesn't
@@ -3542,11 +3559,7 @@ RelationBuildLocalRelation(const char *relname,
 
 	rel->rd_rel->relam = accessmtd;
 
-	if (relkind == RELKIND_RELATION ||
-		relkind == RELKIND_SEQUENCE ||
-		relkind == RELKIND_TOASTVALUE ||
-		relkind == RELKIND_MATVIEW)
-		RelationInitTableAccessMethod(rel);
+	RelationInitTableAccessMethod(rel);
 
 	/*
 	 * Okay to insert into the relcache hash table.
@@ -4106,10 +4119,8 @@ RelationCacheInitializePhase3(void)
 
 		/* Reload tableam data if needed */
 		if (relation->rd_tableam == NULL &&
-			(relation->rd_rel->relkind == RELKIND_RELATION ||
-			 relation->rd_rel->relkind == RELKIND_SEQUENCE ||
-			 relation->rd_rel->relkind == RELKIND_TOASTVALUE ||
-			 relation->rd_rel->relkind == RELKIND_MATVIEW))
+			relation->rd_rel->relkind != RELKIND_INDEX &&
+			relation->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
 		{
 			RelationInitTableAccessMethod(relation);
 			Assert(relation->rd_tableam != NULL);
@@ -5883,11 +5894,7 @@ load_relcache_init_file(bool shared)
 				nailed_rels++;
 
 			/* Load table AM data */
-			if (rel->rd_rel->relkind == RELKIND_RELATION ||
-				rel->rd_rel->relkind == RELKIND_SEQUENCE ||
-				rel->rd_rel->relkind == RELKIND_TOASTVALUE ||
-				rel->rd_rel->relkind == RELKIND_MATVIEW)
-				RelationInitTableAccessMethod(rel);
+			RelationInitTableAccessMethod(rel);
 
 			Assert(rel->rd_index == NULL);
 			Assert(rel->rd_indextuple == NULL);
diff --git a/src/test/regress/expected/create_am.out b/src/test/regress/expected/create_am.out
index 0dfb26c301..c57057e2f6 100644
--- a/src/test/regress/expected/create_am.out
+++ b/src/test/regress/expected/create_am.out
@@ -129,11 +129,12 @@ ERROR:  function int4in(internal) does not exist
 CREATE ACCESS METHOD bogus TYPE TABLE HANDLER bthandler;
 ERROR:  function bthandler must return type table_am_handler
 SELECT amname, amhandler, amtype FROM pg_am where amtype = 't' ORDER BY 1, 2;
- amname |      amhandler       | amtype 
---------+----------------------+--------
- heap   | heap_tableam_handler | t
- heap2  | heap_tableam_handler | t
-(2 rows)
+   amname   |         amhandler          | amtype 
+------------+----------------------------+--------
+ heap       | heap_tableam_handler       | t
+ heap2      | heap_tableam_handler       | t
+ no_storage | no_storage_tableam_handler | t
+(3 rows)
 
 -- First create tables employing the new AM using USING
 -- plain CREATE TABLE
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 7204fdb0b4..606b1662d4 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4898,31 +4898,33 @@ Indexes:
 -- check printing info about access methods
 \dA
 List of access methods
-  Name  | Type  
---------+-------
- brin   | Index
- btree  | Index
- gin    | Index
- gist   | Index
- hash   | Index
- heap   | Table
- heap2  | Table
- spgist | Index
-(8 rows)
+    Name    | Type  
+------------+-------
+ brin       | Index
+ btree      | Index
+ gin        | Index
+ gist       | Index
+ hash       | Index
+ heap       | Table
+ heap2      | Table
+ no_storage | Table
+ spgist     | Index
+(9 rows)
 
 \dA *
 List of access methods
-  Name  | Type  
---------+-------
- brin   | Index
- btree  | Index
- gin    | Index
- gist   | Index
- hash   | Index
- heap   | Table
- heap2  | Table
- spgist | Index
-(8 rows)
+    Name    | Type  
+------------+-------
+ brin       | Index
+ btree      | Index
+ gin        | Index
+ gist       | Index
+ hash       | Index
+ heap       | Table
+ heap2      | Table
+ no_storage | Table
+ spgist     | Index
+(9 rows)
 
 \dA h*
 List of access methods
@@ -4947,32 +4949,34 @@ List of access methods
 
 \dA: extra argument "bar" ignored
 \dA+
-                             List of access methods
-  Name  | Type  |       Handler        |              Description               
---------+-------+----------------------+----------------------------------------
- brin   | Index | brinhandler          | block range index (BRIN) access method
- btree  | Index | bthandler            | b-tree index access method
- gin    | Index | ginhandler           | GIN index access method
- gist   | Index | gisthandler          | GiST index access method
- hash   | Index | hashhandler          | hash index access method
- heap   | Table | heap_tableam_handler | heap table access method
- heap2  | Table | heap_tableam_handler | 
- spgist | Index | spghandler           | SP-GiST index access method
-(8 rows)
+                                  List of access methods
+    Name    | Type  |          Handler           |              Description               
+------------+-------+----------------------------+----------------------------------------
+ brin       | Index | brinhandler                | block range index (BRIN) access method
+ btree      | Index | bthandler                  | b-tree index access method
+ gin        | Index | ginhandler                 | GIN index access method
+ gist       | Index | gisthandler                | GiST index access method
+ hash       | Index | hashhandler                | hash index access method
+ heap       | Table | heap_tableam_handler       | heap table access method
+ heap2      | Table | heap_tableam_handler       | 
+ no_storage | Table | no_storage_tableam_handler | no_storage table access method
+ spgist     | Index | spghandler                 | SP-GiST index access method
+(9 rows)
 
 \dA+ *
-                             List of access methods
-  Name  | Type  |       Handler        |              Description               
---------+-------+----------------------+----------------------------------------
- brin   | Index | brinhandler          | block range index (BRIN) access method
- btree  | Index | bthandler            | b-tree index access method
- gin    | Index | ginhandler           | GIN index access method
- gist   | Index | gisthandler          | GiST index access method
- hash   | Index | hashhandler          | hash index access method
- heap   | Table | heap_tableam_handler | heap table access method
- heap2  | Table | heap_tableam_handler | 
- spgist | Index | spghandler           | SP-GiST index access method
-(8 rows)
+                                  List of access methods
+    Name    | Type  |          Handler           |              Description               
+------------+-------+----------------------------+----------------------------------------
+ brin       | Index | brinhandler                | block range index (BRIN) access method
+ btree      | Index | bthandler                  | b-tree index access method
+ gin        | Index | ginhandler                 | GIN index access method
+ gist       | Index | gisthandler                | GiST index access method
+ hash       | Index | hashhandler                | hash index access method
+ heap       | Table | heap_tableam_handler       | heap table access method
+ heap2      | Table | heap_tableam_handler       | 
+ no_storage | Table | no_storage_tableam_handler | no_storage table access method
+ spgist     | Index | spghandler                 | SP-GiST index access method
+(9 rows)
 
 \dA+ h*
                      List of access methods
-- 
2.30.0

