Decouple operator classes from index access methods

Started by Emre Hasegeliover 4 years ago6 messages
#1Emre Hasegeli
emre@hasegeli.com
1 attachment(s)

I think we can benefit from higher level operator classes which can
support multiple index implementations. This is achievable by
introducing another type of access method. Here is my idea in SQL:

CREATE ACCESS METHOD ordering
TYPE INTERFACE HANDLER ordering_ifam_handler;

CREATE ACCESS METHOD btree
TYPE INDEX HANDLER bthandler
IMPLEMENTS (ordering);

CREATE OPERATOR CLASS btree_int_ops
FOR TYPE int USING ordering AS
FUNCTION 1 btint4cmp(int, int),
FUNCTION 3 =(int, int);

This can make it easier to develop both new index access methods and
data types. The extensions can provide them without depending on each
other.

The initial implementation is attached. I wrote it only to ask for
feedback. I am happy to work on the missing pieces if the community
supports the idea.

I suggest the initial version to come with 2 new access methods in the
new type: hashing and ordering. We can use those in the functions
that are currently searching for the hash and btree operator classes.

Later, I want to work on developing another access method for
containment. It can support high level operator classes with only SQL
callable functions. GiST, SP-GiST, and BRIN can implement this. We
can allow the new implementations together with the existing ones.

Attachments:

0001-v01-Implement-INTERFACE-access-method.patchapplication/octet-stream; name=0001-v01-Implement-INTERFACE-access-method.patchDownload
From 919b3699a968628b9e1ba4b8238cfa3b984afb5b Mon Sep 17 00:00:00 2001
From: Emre Hasegeli <emre@hasegeli.com>
Date: Sun, 13 Jun 2021 09:14:08 +0300
Subject: [PATCH] [v01] Implement INTERFACE access method

---
 src/backend/access/Makefile                |   3 +-
 src/backend/access/index/amapi.c           |  50 +------
 src/backend/access/interface/Makefile      |  18 +++
 src/backend/access/interface/ifamapi.c     | 154 +++++++++++++++++++++
 src/backend/access/interface/ordering.c    |  39 ++++++
 src/backend/catalog/Makefile               |   2 +-
 src/backend/catalog/index.c                |   4 +-
 src/backend/catalog/objectaddress.c        |   2 +-
 src/backend/commands/amcmds.c              | 126 +++++++++++++++--
 src/backend/commands/indexcmds.c           |  90 +++++++++---
 src/backend/commands/opclasscmds.c         |  15 +-
 src/backend/commands/tablecmds.c           |   2 +-
 src/backend/commands/typecmds.c            |   2 +-
 src/backend/nodes/copyfuncs.c              |   1 +
 src/backend/nodes/equalfuncs.c             |   1 +
 src/backend/parser/gram.y                  |  19 ++-
 src/backend/parser/parse_utilcmd.c         |   6 +-
 src/backend/utils/adt/pseudotypes.c        |   1 +
 src/backend/utils/adt/ruleutils.c          |   2 +-
 src/backend/utils/cache/typcache.c         |   4 +-
 src/bin/pg_dump/pg_dump.c                  |   3 +
 src/bin/psql/describe.c                    |   2 +
 src/include/access/ifam.h                  |  64 +++++++++
 src/include/catalog/pg_am.dat              |   3 +
 src/include/catalog/pg_am.h                |   1 +
 src/include/catalog/pg_amimplements.dat    |  17 +++
 src/include/catalog/pg_amimplements.h      |  51 +++++++
 src/include/catalog/pg_proc.dat            |  13 ++
 src/include/catalog/pg_type.dat            |   6 +
 src/include/commands/defrem.h              |   4 +-
 src/include/nodes/nodes.h                  |   1 +
 src/include/nodes/parsenodes.h             |   1 +
 src/include/parser/kwlist.h                |   2 +
 src/test/regress/expected/amutils.out      |   8 +-
 src/test/regress/expected/create_am.out    |  12 +-
 src/test/regress/expected/oidjoins.out     |   2 +
 src/test/regress/expected/psql.out         | 100 +++++++------
 src/test/regress/expected/sanity_check.out |   1 +
 src/test/regress/sql/create_am.sql         |   9 +-
 39 files changed, 691 insertions(+), 150 deletions(-)
 create mode 100644 src/backend/access/interface/Makefile
 create mode 100644 src/backend/access/interface/ifamapi.c
 create mode 100644 src/backend/access/interface/ordering.c
 create mode 100644 src/include/access/ifam.h
 create mode 100644 src/include/catalog/pg_amimplements.dat
 create mode 100644 src/include/catalog/pg_amimplements.h

diff --git a/src/backend/access/Makefile b/src/backend/access/Makefile
index 0880e0a8bb..0e02299c7c 100644
--- a/src/backend/access/Makefile
+++ b/src/backend/access/Makefile
@@ -1,14 +1,15 @@
 #
 # Makefile for the access methods module
 #
 # src/backend/access/Makefile
 #
 
 subdir = src/backend/access
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-SUBDIRS	    = brin common gin gist hash heap index nbtree rmgrdesc spgist \
+SUBDIRS	    = brin common gin gist hash heap index interface \
+			  nbtree rmgrdesc spgist \
 			  table tablesample transam
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/index/amapi.c b/src/backend/access/index/amapi.c
index d30bc43514..e9dbbf4e1e 100644
--- a/src/backend/access/index/amapi.c
+++ b/src/backend/access/index/amapi.c
@@ -9,22 +9,20 @@
  * IDENTIFICATION
  *	  src/backend/access/index/amapi.c
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
 #include "access/amapi.h"
 #include "access/htup_details.h"
 #include "catalog/pg_am.h"
-#include "catalog/pg_opclass.h"
-#include "utils/builtins.h"
 #include "utils/syscache.h"
 
 
 /*
  * GetIndexAmRoutine - call the specified access method handler routine to get
  * its IndexAmRoutine struct, which will be palloc'd in the caller's context.
  *
  * Note that if the amhandler function is built-in, this will not involve
  * any catalog access.  It's therefore safe to use this while bootstrapping
  * indexes for the system catalogs.  relcache.c relies on that.
@@ -71,73 +69,33 @@ GetIndexAmRoutineByAmId(Oid amoid, bool noerror)
 	amform = (Form_pg_am) GETSTRUCT(tuple);
 
 	/* Check if it's an index access method as opposed to some other AM */
 	if (amform->amtype != AMTYPE_INDEX)
 	{
 		if (noerror)
 		{
 			ReleaseSysCache(tuple);
 			return NULL;
 		}
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("access method \"%s\" is not of type %s",
-						NameStr(amform->amname), "INDEX")));
+		elog(ERROR, "access method \"%s\" is not of type INDEX",
+			 NameStr(amform->amname));
 	}
 
 	amhandler = amform->amhandler;
 
 	/* Complain if handler OID is invalid */
 	if (!RegProcedureIsValid(amhandler))
 	{
 		if (noerror)
 		{
 			ReleaseSysCache(tuple);
 			return NULL;
 		}
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("index access method \"%s\" does not have a handler",
-						NameStr(amform->amname))));
+		elog(ERROR, "access method \"%s\" does not have a handler",
+			 NameStr(amform->amname));
 	}
 
 	ReleaseSysCache(tuple);
 
 	/* And finally, call the handler function to get the API struct. */
 	return GetIndexAmRoutine(amhandler);
 }
-
-
-/*
- * Ask appropriate access method to validate the specified opclass.
- */
-Datum
-amvalidate(PG_FUNCTION_ARGS)
-{
-	Oid			opclassoid = PG_GETARG_OID(0);
-	bool		result;
-	HeapTuple	classtup;
-	Form_pg_opclass classform;
-	Oid			amoid;
-	IndexAmRoutine *amroutine;
-
-	classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassoid));
-	if (!HeapTupleIsValid(classtup))
-		elog(ERROR, "cache lookup failed for operator class %u", opclassoid);
-	classform = (Form_pg_opclass) GETSTRUCT(classtup);
-
-	amoid = classform->opcmethod;
-
-	ReleaseSysCache(classtup);
-
-	amroutine = GetIndexAmRoutineByAmId(amoid, false);
-
-	if (amroutine->amvalidate == NULL)
-		elog(ERROR, "function amvalidate is not defined for index access method %u",
-			 amoid);
-
-	result = amroutine->amvalidate(opclassoid);
-
-	pfree(amroutine);
-
-	PG_RETURN_BOOL(result);
-}
diff --git a/src/backend/access/interface/Makefile b/src/backend/access/interface/Makefile
new file mode 100644
index 0000000000..1cc71b1685
--- /dev/null
+++ b/src/backend/access/interface/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for access/interface
+#
+# IDENTIFICATION
+#    src/backend/access/interface/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/access/interface
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+	ifamapi.o ordering.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/interface/ifamapi.c b/src/backend/access/interface/ifamapi.c
new file mode 100644
index 0000000000..8822d51bc7
--- /dev/null
+++ b/src/backend/access/interface/ifamapi.c
@@ -0,0 +1,154 @@
+/*-------------------------------------------------------------------------
+ *
+ * ifamapi.c
+ *	  interface access method routines
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/interface/ifamapi.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/htup_details.h"
+#include "access/ifam.h"
+#include "catalog/pg_am.h"
+#include "catalog/pg_opclass.h"
+#include "utils/fmgrprotos.h"
+#include "utils/syscache.h"
+
+/*
+ * GetInterfaceAmRoutine - call the specified access method handler routine
+ * to get its InterfaceAmRoutine struct, which will be palloc'd in the caller's
+ * context.
+ *
+ * Note that if the amhandler function is built-in, this will not involve
+ * any catalog access.  It's therefore safe to use this while bootstrapping
+ * indexes for the system catalogs.  relcache.c relies on that.
+ */
+static InterfaceAmRoutine *
+GetInterfaceAmRoutine(Oid amhandler)
+{
+	Datum		datum;
+	InterfaceAmRoutine *routine;
+
+	datum = OidFunctionCall0(amhandler);
+	routine = (InterfaceAmRoutine *) DatumGetPointer(datum);
+
+	if (routine == NULL || !IsA(routine, InterfaceAmRoutine))
+		elog(ERROR, "interface access method handler function %u did not return an InterfaceAmRoutine struct",
+			 amhandler);
+
+	return routine;
+}
+
+/*
+ * TranslateIndexToInterfaceAmRoutine - gets the IndexAmRoutine struct and
+ * turnes it to InterfaceAmRoutine, which will be palloc'd in the caller's
+ * context.
+ */
+static InterfaceAmRoutine *
+GetInterfaceAmRoutineFromIndexAmHander(Oid amhandler)
+{
+	IndexAmRoutine *indexroutine;
+	InterfaceAmRoutine *ifroutine;
+
+	indexroutine = GetIndexAmRoutine(amhandler);
+
+	ifroutine = makeNode(InterfaceAmRoutine);
+
+	ifroutine->amstrategies = indexroutine->amstrategies;
+	ifroutine->amsupport = indexroutine->amsupport;
+	ifroutine->amoptsprocnum = indexroutine->amoptsprocnum;
+	ifroutine->amcanorder = indexroutine->amcanorder;
+	ifroutine->amcanorderbyop = indexroutine->amcanorderbyop;
+	ifroutine->amvalidate = indexroutine->amvalidate;
+	ifroutine->amadjustmembers = indexroutine->amadjustmembers;
+
+	pfree(indexroutine);
+
+	return ifroutine;
+}
+
+/*
+ * GetInterfaceAmRoutineByAmId - look up the handler of the index access method
+ * with the given OID, and get its InterfaceAmRoutine struct.
+ */
+InterfaceAmRoutine *
+GetInterfaceAmRoutineByAmId(Oid amoid)
+{
+	char		amtype;
+	HeapTuple	tuple;
+	Form_pg_am	amform;
+	regproc		amhandler;
+
+	/* Get handler function OID for the access method */
+	tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for access method %u",
+			 amoid);
+
+	amform = (Form_pg_am) GETSTRUCT(tuple);
+	amtype = amform->amtype;
+
+	/* Check if it's an index access method as opposed to some other AM */
+	if (amtype != AMTYPE_INTERFACE && amtype != AMTYPE_INDEX)
+		elog(ERROR, "access method \"%s\" is not of type INDEX or INTERFACE",
+			 NameStr(amform->amname));
+
+	amhandler = amform->amhandler;
+
+	/* Complain if handler OID is invalid */
+	if (!RegProcedureIsValid(amhandler))
+		elog(ERROR, "access method \"%s\" does not have a handler",
+			 NameStr(amform->amname));
+
+	ReleaseSysCache(tuple);
+
+	/* And finally, call the handler function to get the API struct */
+	if (amtype == AMTYPE_INTERFACE)
+		return GetInterfaceAmRoutine(amhandler);
+	else
+		return GetInterfaceAmRoutineFromIndexAmHander(amhandler);
+}
+
+/*
+ * Ask appropriate access method to validate the specified opclass.
+ */
+Datum
+amvalidate(PG_FUNCTION_ARGS)
+{
+	Oid			opclassoid = PG_GETARG_OID(0);
+	bool		result;
+	HeapTuple	classtup;
+	Form_pg_opclass classform;
+	Oid			amoid;
+	InterfaceAmRoutine *amroutine;
+
+	classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassoid));
+	if (!HeapTupleIsValid(classtup))
+		elog(ERROR, "cache lookup failed for operator class %u", opclassoid);
+	classform = (Form_pg_opclass) GETSTRUCT(classtup);
+
+	amoid = classform->opcmethod;
+
+	ReleaseSysCache(classtup);
+
+	amroutine = GetInterfaceAmRoutineByAmId(amoid);
+
+	if (amroutine->amvalidate == NULL)
+		elog(ERROR, "function amvalidate is not defined for index access method %u",
+			 amoid);
+
+	result = amroutine->amvalidate(opclassoid);
+
+	pfree(amroutine);
+
+	PG_RETURN_BOOL(result);
+}
diff --git a/src/backend/access/interface/ordering.c b/src/backend/access/interface/ordering.c
new file mode 100644
index 0000000000..ddd322af12
--- /dev/null
+++ b/src/backend/access/interface/ordering.c
@@ -0,0 +1,39 @@
+/*-------------------------------------------------------------------------
+ *
+ * ordering.c
+ *	  ordering interface access method routines
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/interface/ordering.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/ifam.h"
+#include "access/nbtree.h"
+#include "utils/fmgrprotos.h"
+
+/*
+ * Ordering handler function: return InterfaceAmRoutine with access method
+ * parameters and callbacks.
+ */
+Datum
+ordering_ifam_handler(PG_FUNCTION_ARGS)
+{
+	InterfaceAmRoutine *amroutine = makeNode(InterfaceAmRoutine);
+
+	amroutine->amstrategies = BTMaxStrategyNumber;
+	amroutine->amsupport = BTNProcs;
+	amroutine->amoptsprocnum = BTOPTIONS_PROC;
+	amroutine->amcanorder = true;
+	amroutine->amcanorderbyop = false;
+	amroutine->amvalidate = btvalidate;
+
+	PG_RETURN_POINTER(amroutine);
+}
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 69f9dd51a7..ee2fe6e99e 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -47,21 +47,21 @@ OBJS = \
 
 include $(top_srcdir)/src/backend/common.mk
 
 # Note: the order of this list determines the order in which the catalog
 # header files are assembled into postgres.bki.  BKI_BOOTSTRAP catalogs
 # must appear first, and pg_statistic before pg_statistic_ext_data, and
 # there are reputedly other, undocumented ordering dependencies.
 CATALOG_HEADERS := \
 	pg_proc.h pg_type.h pg_attribute.h pg_class.h \
 	pg_attrdef.h pg_constraint.h pg_inherits.h pg_index.h pg_operator.h \
-	pg_opfamily.h pg_opclass.h pg_am.h pg_amop.h pg_amproc.h \
+	pg_opfamily.h pg_opclass.h pg_am.h pg_amimplements.h pg_amop.h pg_amproc.h \
 	pg_language.h pg_largeobject_metadata.h pg_largeobject.h pg_aggregate.h \
 	pg_statistic.h pg_statistic_ext.h pg_statistic_ext_data.h \
 	pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
 	pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
 	pg_database.h pg_db_role_setting.h pg_tablespace.h \
 	pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
 	pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
 	pg_ts_parser.h pg_ts_template.h pg_extension.h \
 	pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
 	pg_foreign_table.h pg_policy.h pg_replication_origin.h \
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 50b7a16bce..b2b308202a 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -384,21 +384,21 @@ ConstructTupleDescriptor(Relation heapRelation,
 			 * we actually need to compress a value, we'll use whatever the
 			 * current value of default_toast_compression is at that point in
 			 * time.
 			 */
 			to->attcompression = InvalidCompressionMethod;
 
 			ReleaseSysCache(tuple);
 
 			/*
 			 * Make sure the expression yields a type that's safe to store in
-			 * an index.  We need this defense because we have index opclasses
+			 * an index.  We need this defense because we have opclasses
 			 * for pseudo-types such as "record", and the actually stored type
 			 * had better be safe; eg, a named composite type is okay, an
 			 * anonymous record type is not.  The test is the same as for
 			 * whether a table column is of a safe type (which is why we
 			 * needn't check for the non-expression case).
 			 */
 			CheckAttributeType(NameStr(to->attname),
 							   to->atttypid, to->attcollation,
 							   NIL, 0);
 		}
@@ -659,21 +659,21 @@ UpdateIndexRelation(Oid indexoid,
  *		parent index; otherwise InvalidOid.
  * parentConstraintId: if creating a constraint on a partition, the OID
  *		of the constraint in the parent; otherwise InvalidOid.
  * relFileNode: normally, pass InvalidOid to get new storage.  May be
  *		nonzero to attach an existing valid build.
  * indexInfo: same info executor uses to insert into the index
  * indexColNames: column names to use for index (List of char *)
  * accessMethodObjectId: OID of index AM to use
  * tableSpaceId: OID of tablespace to use
  * collationObjectId: array of collation OIDs, one per index column
- * classObjectId: array of index opclass OIDs, one per index column
+ * classObjectId: array of opclass OIDs, one per index column
  * coloptions: array of per-index-column indoption settings
  * reloptions: AM-specific options
  * flags: bitmask that can include any combination of these bits:
  *		INDEX_CREATE_IS_PRIMARY
  *			the index is a primary key
  *		INDEX_CREATE_ADD_CONSTRAINT:
  *			invoke index_constraint_create also
  *		INDEX_CREATE_SKIP_BUILD:
  *			skip the index_build() step for the moment; caller must do it
  *			later (typically via reindex_index())
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9882e549c4..beeda85c33 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1665,21 +1665,21 @@ get_object_address_type(ObjectType objtype, TypeName *typename, bool missing_ok)
 /*
  * Find the ObjectAddress for an opclass or opfamily.
  */
 static ObjectAddress
 get_object_address_opcf(ObjectType objtype, List *object, bool missing_ok)
 {
 	Oid			amoid;
 	ObjectAddress address;
 
 	/* XXX no missing_ok support here */
-	amoid = get_index_am_oid(strVal(linitial(object)), false);
+	amoid = get_interface_or_index_am_oid(strVal(linitial(object)), false);
 	object = list_copy_tail(object, 1);
 
 	switch (objtype)
 	{
 		case OBJECT_OPCLASS:
 			address.classId = OperatorClassRelationId;
 			address.objectId = get_opclass_oid(amoid, object, missing_ok);
 			address.objectSubId = 0;
 			break;
 		case OBJECT_OPFAMILY:
diff --git a/src/backend/commands/amcmds.c b/src/backend/commands/amcmds.c
index 188109e474..956e8bdec4 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -13,50 +13,55 @@
  */
 #include "postgres.h"
 
 #include "access/htup_details.h"
 #include "access/table.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_amimplements.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "miscadmin.h"
 #include "parser/parse_func.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
 
+static void StoreCatalogAmimpments(Oid implementing, List *implements);
 static Oid	lookup_am_handler_func(List *handler_name, char amtype);
+static Oid get_am_type_oid(const char *amname, char amtype, char amtype2, bool missing_ok);
 static const char *get_am_type_string(char amtype);
 
 
 /*
  * CreateAccessMethod
  *		Registers a new access method.
  */
 ObjectAddress
 CreateAccessMethod(CreateAmStmt *stmt)
 {
 	Relation	rel;
 	ObjectAddress myself;
 	ObjectAddress referenced;
 	Oid			amoid;
 	Oid			amhandler;
 	bool		nulls[Natts_pg_am];
 	Datum		values[Natts_pg_am];
 	HeapTuple	tup;
+	List	   *implementsoids;
+	ListCell   *listptr;
 
 	rel = table_open(AccessMethodRelationId, RowExclusiveLock);
 
 	/* Must be super user */
 	if (!superuser())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 errmsg("permission denied to create access method \"%s\"",
 						stmt->amname),
 				 errhint("Must be superuser to create an access method.")));
@@ -70,20 +75,44 @@ CreateAccessMethod(CreateAmStmt *stmt)
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("access method \"%s\" already exists",
 						stmt->amname)));
 	}
 
 	/*
 	 * Get the handler function oid, verifying the AM type while at it.
 	 */
 	amhandler = lookup_am_handler_func(stmt->handler_name, stmt->amtype);
 
+	/*
+	 * Determine the list of OIDs of the implemented access methods
+	 */
+	implementsoids = NIL;
+	foreach(listptr, stmt->implements)
+	{
+		char	   *name = strVal(lfirst(listptr));
+		Oid			oid = get_am_type_oid(name, AMTYPE_INTERFACE, '\0', false);
+
+		/* Reject duplications */
+		if (list_member_oid(implementsoids, oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_TABLE),
+					 errmsg("access method \"%s\" would be implemented more than once",
+							name)));
+
+		implementsoids = lappend_oid(implementsoids, oid);
+	}
+
+	if (implementsoids != NIL && stmt->amtype != AMTYPE_INDEX)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("only index access methods can implement interfaces")));
+
 	/*
 	 * Insert tuple into pg_am.
 	 */
 	memset(values, 0, sizeof(values));
 	memset(nulls, false, sizeof(nulls));
 
 	amoid = GetNewOidWithIndex(rel, AmOidIndexId, Anum_pg_am_oid);
 	values[Anum_pg_am_oid - 1] = ObjectIdGetDatum(amoid);
 	values[Anum_pg_am_amname - 1] =
 		DirectFunctionCall1(namein, CStringGetDatum(stmt->amname));
@@ -105,91 +134,157 @@ CreateAccessMethod(CreateAmStmt *stmt)
 	referenced.objectSubId = 0;
 
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 	recordDependencyOnCurrentExtension(&myself, false);
 
 	InvokeObjectPostCreateHook(AccessMethodRelationId, amoid, 0);
 
 	table_close(rel, RowExclusiveLock);
 
+	StoreCatalogAmimpments(amoid, implementsoids);
+
 	return myself;
 }
 
+/*
+ * StoreCatalogAmimplements
+ *      Updates the system catalogs with proper implements information.
+ */
+static void
+StoreCatalogAmimpments(Oid implementing, List *implements)
+{
+	int			seq;
+	ListCell   *listptr;
+	Relation	amirelation;
+	ObjectAddress childobject,
+				parentobject;
+	Datum		values[Natts_pg_amimplements];
+	bool		nulls[Natts_pg_amimplements];
+	HeapTuple	tuple;
+
+	AssertArg(OidIsValid(implementing));
+
+	/* Prepare to insert into pg_amimplements */
+	amirelation = table_open(AmimplementsRelationId, RowExclusiveLock);
+	values[Anum_pg_amimplements_amiamid - 1] = ObjectIdGetDatum(implementing);
+	memset(nulls, 0, sizeof(nulls));
+
+	/* Prepare the dependency objects */
+	parentobject.classId = AccessMethodRelationId;
+	parentobject.objectSubId = 0;
+	childobject.classId = AccessMethodRelationId;
+	childobject.objectId = implementing;
+	childobject.objectSubId = 0;
+
+	seq = 1;
+	foreach(listptr, implements)
+	{
+		Oid			oid = lfirst_oid(listptr);
+
+		AssertArg(OidIsValid(listptr));
+
+		/* Store pg_amimplements entry */
+		values[Anum_pg_amimplements_amiparent - 1] = ObjectIdGetDatum(oid);
+		values[Anum_pg_amimplements_amiseqno - 1] = Int32GetDatum(seq);
+		tuple = heap_form_tuple(RelationGetDescr(amirelation), values, nulls);
+		CatalogTupleInsert(amirelation, tuple);
+		heap_freetuple(tuple);
+
+		/* Store a dependency too */
+		parentobject.objectId = oid;
+		recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);
+
+		seq++;
+	}
+
+	table_close(amirelation, RowExclusiveLock);
+}
+
 /*
  * get_am_type_oid
  *		Worker for various get_am_*_oid variants
  *
  * If missing_ok is false, throw an error if access method not found.  If
  * true, just return InvalidOid.
  *
  * If amtype is not '\0', an error is raised if the AM found is not of the
- * given type.
+ * given types.
  */
 static Oid
-get_am_type_oid(const char *amname, char amtype, bool missing_ok)
+get_am_type_oid(const char *amname, char amtype, char amtype2, bool missing_ok)
 {
 	HeapTuple	tup;
 	Oid			oid = InvalidOid;
 
 	tup = SearchSysCache1(AMNAME, CStringGetDatum(amname));
 	if (HeapTupleIsValid(tup))
 	{
 		Form_pg_am	amform = (Form_pg_am) GETSTRUCT(tup);
 
 		if (amtype != '\0' &&
 			amform->amtype != amtype)
-			ereport(ERROR,
-					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-					 errmsg("access method \"%s\" is not of type %s",
-							NameStr(amform->amname),
-							get_am_type_string(amtype))));
+		{
+			if (amtype2 == '\0')
+				ereport(ERROR,
+						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						 errmsg("access method \"%s\" is not of type %s",
+								NameStr(amform->amname),
+								get_am_type_string(amtype))));
+			else if (amform->amtype != amtype2)
+				ereport(ERROR,
+						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						 errmsg("access method \"%s\" is not of type %s or %s",
+								NameStr(amform->amname),
+								get_am_type_string(amtype),
+								get_am_type_string(amtype2))));
+		}
 
 		oid = amform->oid;
 		ReleaseSysCache(tup);
 	}
 
 	if (!OidIsValid(oid) && !missing_ok)
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("access method \"%s\" does not exist", amname)));
 	return oid;
 }
 
 /*
- * get_index_am_oid - given an access method name, look up its OID
- *		and verify it corresponds to an index AM.
+ * get_interface_or_index_am_oid - given an access method name, look up its OID
+ *		and verify it corresponds to an index or interface AM.
  */
 Oid
-get_index_am_oid(const char *amname, bool missing_ok)
+get_interface_or_index_am_oid(const char *amname, bool missing_ok)
 {
-	return get_am_type_oid(amname, AMTYPE_INDEX, missing_ok);
+	return get_am_type_oid(amname, AMTYPE_INTERFACE, AMTYPE_INDEX, missing_ok);
 }
 
 /*
  * get_table_am_oid - given an access method name, look up its OID
  *		and verify it corresponds to an table AM.
  */
 Oid
 get_table_am_oid(const char *amname, bool missing_ok)
 {
-	return get_am_type_oid(amname, AMTYPE_TABLE, missing_ok);
+	return get_am_type_oid(amname, AMTYPE_TABLE, '\0', missing_ok);
 }
 
 /*
  * get_am_oid - given an access method name, look up its OID.
  *		The type is not checked.
  */
 Oid
 get_am_oid(const char *amname, bool missing_ok)
 {
-	return get_am_type_oid(amname, '\0', missing_ok);
+	return get_am_type_oid(amname, '\0', '\0', missing_ok);
 }
 
 /*
  * get_am_name - given an access method OID, look up its name.
  */
 char *
 get_am_name(Oid amOid)
 {
 	HeapTuple	tup;
 	char	   *result = NULL;
@@ -206,20 +301,22 @@ get_am_name(Oid amOid)
 }
 
 /*
  * Convert single-character access method type into string for error reporting.
  */
 static const char *
 get_am_type_string(char amtype)
 {
 	switch (amtype)
 	{
+		case AMTYPE_INTERFACE:
+			return "INTERFACE";
 		case AMTYPE_INDEX:
 			return "INDEX";
 		case AMTYPE_TABLE:
 			return "TABLE";
 		default:
 			/* shouldn't happen */
 			elog(ERROR, "invalid access method type '%c'", amtype);
 			return NULL;		/* keep compiler quiet */
 	}
 }
@@ -241,20 +338,23 @@ lookup_am_handler_func(List *handler_name, char amtype)
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_FUNCTION),
 				 errmsg("handler function is not specified")));
 
 	/* handlers have one argument of type internal */
 	handlerOid = LookupFuncName(handler_name, 1, funcargtypes, false);
 
 	/* check that handler has the correct return type */
 	switch (amtype)
 	{
+		case AMTYPE_INTERFACE:
+			expectedType = INTERFACE_AM_HANDLEROID;
+			break;
 		case AMTYPE_INDEX:
 			expectedType = INDEX_AM_HANDLEROID;
 			break;
 		case AMTYPE_TABLE:
 			expectedType = TABLE_AM_HANDLEROID;
 			break;
 		default:
 			elog(ERROR, "unrecognized access method type \"%c\"", amtype);
 	}
 
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 76774dce06..e68ff3b6ab 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -19,20 +19,21 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/reloptions.h"
 #include "access/sysattr.h"
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_amimplements.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
 #include "commands/comment.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
@@ -1997,41 +1998,80 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 
 			indexInfo->ii_OpclassOptions[attn] =
 				transformRelOptions((Datum) 0, attribute->opclassopts,
 									NULL, NULL, false, false);
 		}
 
 		attn++;
 	}
 }
 
+List *
+GetCompatibleAccessMethods(indexAccessMethodId)
+{
+	List	   *result;
+	Relation	rel;
+	ScanKeyData	skey[1];
+	SysScanDesc	scan;
+	HeapTuple	tup;
+
+	result = list_make1_oid(indexAccessMethodId);
+
+	rel = table_open(AmimplementsRelationId, AccessShareLock);
+
+	ScanKeyInit(&skey[0],
+				Anum_pg_amimplements_amiamid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(indexAccessMethodId));
+
+	scan = systable_beginscan(rel, AmimplementsAmidSeqnoIndexId, true,
+							  NULL, 1, skey);
+
+	while (HeapTupleIsValid(tup = systable_getnext(scan)))
+	{
+		Form_pg_amimplements amimplements = (Form_pg_amimplements) GETSTRUCT(tup);
+
+		result = lappend_oid(result, amimplements->amiparent);
+	}
+
+	systable_endscan(scan);
+
+	table_close(rel, AccessShareLock);
+
+	return result;
+}
+
 /*
  * Resolve possibly-defaulted operator class specification
  *
  * Note: This is used to resolve operator class specifications in index and
  * partition key definitions.
  */
 Oid
 ResolveOpClass(List *opclass, Oid attrType,
 			   const char *accessMethodName, Oid accessMethodId)
 {
 	char	   *schemaname;
 	char	   *opcname;
+	List	   *amoids;
+	ListCell   *listptr;
 	HeapTuple	tuple;
 	Form_pg_opclass opform;
 	Oid			opClassId,
 				opInputType;
 
+	amoids = GetCompatibleAccessMethods(accessMethodId);
+
 	if (opclass == NIL)
 	{
 		/* no operator class specified, so find the default */
-		opClassId = GetDefaultOpClass(attrType, accessMethodId);
+		opClassId = GetDefaultOpClass(attrType, amoids);
 		if (!OidIsValid(opClassId))
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_OBJECT),
 					 errmsg("data type %s has no default operator class for access method \"%s\"",
 							format_type_be(attrType), accessMethodName),
 					 errhint("You must specify an operator class for the index or define a default operator class for the data type.")));
 		return opClassId;
 	}
 
 	/*
@@ -2040,29 +2080,41 @@ ResolveOpClass(List *opclass, Oid attrType,
 
 	/* deconstruct the name list */
 	DeconstructQualifiedName(opclass, &schemaname, &opcname);
 
 	if (schemaname)
 	{
 		/* Look in specific schema only */
 		Oid			namespaceId;
 
 		namespaceId = LookupExplicitNamespace(schemaname, false);
-		tuple = SearchSysCache3(CLAAMNAMENSP,
-								ObjectIdGetDatum(accessMethodId),
-								PointerGetDatum(opcname),
-								ObjectIdGetDatum(namespaceId));
+
+		foreach(listptr, amoids)
+		{
+			tuple = SearchSysCache3(CLAAMNAMENSP,
+									ObjectIdGetDatum(lfirst_oid(listptr)),
+									PointerGetDatum(opcname),
+									ObjectIdGetDatum(namespaceId));
+			if (HeapTupleIsValid(tuple))
+				break;
+		}
 	}
 	else
 	{
 		/* Unqualified opclass name, so search the search path */
-		opClassId = OpclassnameGetOpcid(accessMethodId, opcname);
+		foreach(listptr, amoids)
+		{
+			opClassId = OpclassnameGetOpcid(lfirst_oid(listptr), opcname);
+			if (OidIsValid(opClassId))
+				break;
+		}
+
 		if (!OidIsValid(opClassId))
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_OBJECT),
 					 errmsg("operator class \"%s\" does not exist for access method \"%s\"",
 							opcname, accessMethodName)));
 		tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opClassId));
 	}
 
 	if (!HeapTupleIsValid(tuple))
 		ereport(ERROR,
@@ -2085,32 +2137,31 @@ ResolveOpClass(List *opclass, Oid attrType,
 						NameListToString(opclass), format_type_be(attrType))));
 
 	ReleaseSysCache(tuple);
 
 	return opClassId;
 }
 
 /*
  * GetDefaultOpClass
  *
- * Given the OIDs of a datatype and an access method, find the default
+ * Given the OIDs of a datatype and access methods, find the default
  * operator class, if any.  Returns InvalidOid if there is none.
  */
 Oid
-GetDefaultOpClass(Oid type_id, Oid am_id)
+GetDefaultOpClass(Oid type_id, List *am_ids)
 {
 	Oid			result = InvalidOid;
 	int			nexact = 0;
 	int			ncompatible = 0;
 	int			ncompatiblepreferred = 0;
 	Relation	rel;
-	ScanKeyData skey[1];
 	SysScanDesc scan;
 	HeapTuple	tup;
 	TYPCATEGORY tcategory;
 
 	/* If it's a domain, look at the base type instead */
 	type_id = getBaseType(type_id);
 
 	tcategory = TypeCategory(type_id);
 
 	/*
@@ -2120,34 +2171,39 @@ GetDefaultOpClass(Oid type_id, Oid am_id)
 	 *
 	 * We could find more than one binary-compatible match.  If just one is
 	 * for a preferred type, use that one; otherwise we fail, forcing the user
 	 * to specify which one he wants.  (The preferred-type special case is a
 	 * kluge for varchar: it's binary-compatible to both text and bpchar, so
 	 * we need a tiebreaker.)  If we find more than one exact match, then
 	 * someone put bogus entries in pg_opclass.
 	 */
 	rel = table_open(OperatorClassRelationId, AccessShareLock);
 
-	ScanKeyInit(&skey[0],
-				Anum_pg_opclass_opcmethod,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(am_id));
-
-	scan = systable_beginscan(rel, OpclassAmNameNspIndexId, true,
-							  NULL, 1, skey);
+	scan = systable_beginscan(rel, InvalidOid, false, NULL, 0, NULL);
 
 	while (HeapTupleIsValid(tup = systable_getnext(scan)))
 	{
+		bool		ammatch = false;
+		ListCell   *listptr;
 		Form_pg_opclass opclass = (Form_pg_opclass) GETSTRUCT(tup);
 
+		foreach(listptr, am_ids)
+		{
+			if (lfirst_oid(listptr) == opclass->opcmethod)
+			{
+				ammatch = true;
+				break;
+			}
+		}
+
 		/* ignore altogether if not a default opclass */
-		if (!opclass->opcdefault)
+		if (!ammatch || !opclass->opcdefault)
 			continue;
 		if (opclass->opcintype == type_id)
 		{
 			nexact++;
 			result = opclass->oid;
 		}
 		else if (nexact == 0 &&
 				 IsBinaryCoercible(type_id, opclass->opcintype))
 		{
 			if (IsPreferredType(tcategory, opclass->opcintype))
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index fad39e2b75..5da2cb01e1 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -13,20 +13,21 @@
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
 #include <limits.h>
 
 #include "access/genam.h"
 #include "access/hash.h"
 #include "access/htup_details.h"
+#include "access/ifam.h"
 #include "access/nbtree.h"
 #include "access/sysattr.h"
 #include "access/table.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_amproc.h"
@@ -337,21 +338,21 @@ DefineOpClass(CreateOpClassStmt *stmt)
 	int			maxOpNumber,	/* amstrategies value */
 				optsProcNumber, /* amoptsprocnum value */
 				maxProcNumber;	/* amsupport value */
 	bool		amstorage;		/* amstorage flag */
 	List	   *operators;		/* OpFamilyMember list for operators */
 	List	   *procedures;		/* OpFamilyMember list for support procs */
 	ListCell   *l;
 	Relation	rel;
 	HeapTuple	tup;
 	Form_pg_am	amform;
-	IndexAmRoutine *amroutine;
+	InterfaceAmRoutine *amroutine;
 	Datum		values[Natts_pg_opclass];
 	bool		nulls[Natts_pg_opclass];
 	AclResult	aclresult;
 	NameData	opcName;
 	ObjectAddress myself,
 				referenced;
 
 	/* Convert list of names to a name and namespace */
 	namespaceoid = QualifiedNameGetCreationNamespace(stmt->opclassname,
 													 &opcname);
@@ -365,21 +366,21 @@ DefineOpClass(CreateOpClassStmt *stmt)
 	/* Get necessary info about access method */
 	tup = SearchSysCache1(AMNAME, CStringGetDatum(stmt->amname));
 	if (!HeapTupleIsValid(tup))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("access method \"%s\" does not exist",
 						stmt->amname)));
 
 	amform = (Form_pg_am) GETSTRUCT(tup);
 	amoid = amform->oid;
-	amroutine = GetIndexAmRoutineByAmId(amoid, false);
+	amroutine = GetInterfaceAmRoutineByAmId(amoid);
 	ReleaseSysCache(tup);
 
 	maxOpNumber = amroutine->amstrategies;
 	/* if amstrategies is zero, just enforce that op numbers fit in int16 */
 	if (maxOpNumber <= 0)
 		maxOpNumber = SHRT_MAX;
 	maxProcNumber = amroutine->amsupport;
 	optsProcNumber = amroutine->amoptsprocnum;
 	amstorage = amroutine->amstorage;
 
@@ -771,21 +772,21 @@ DefineOpFamily(CreateOpFamilyStmt *stmt)
 	namespaceoid = QualifiedNameGetCreationNamespace(stmt->opfamilyname,
 													 &opfname);
 
 	/* Check we have creation rights in target namespace */
 	aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE);
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, OBJECT_SCHEMA,
 					   get_namespace_name(namespaceoid));
 
 	/* Get access method OID, throwing an error if it doesn't exist. */
-	amoid = get_index_am_oid(stmt->amname, false);
+	amoid = get_interface_or_index_am_oid(stmt->amname, false);
 
 	/* XXX Should we make any privilege check against the AM? */
 
 	/*
 	 * Currently, we require superuser privileges to create an opfamily. See
 	 * comments in DefineOpClass.
 	 */
 	if (!superuser())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -807,33 +808,33 @@ DefineOpFamily(CreateOpFamilyStmt *stmt)
 Oid
 AlterOpFamily(AlterOpFamilyStmt *stmt)
 {
 	Oid			amoid,			/* our AM's oid */
 				opfamilyoid;	/* oid of opfamily */
 	int			maxOpNumber,	/* amstrategies value */
 				optsProcNumber, /* amopclassopts value */
 				maxProcNumber;	/* amsupport value */
 	HeapTuple	tup;
 	Form_pg_am	amform;
-	IndexAmRoutine *amroutine;
+	InterfaceAmRoutine *amroutine;
 
 	/* Get necessary info about access method */
 	tup = SearchSysCache1(AMNAME, CStringGetDatum(stmt->amname));
 	if (!HeapTupleIsValid(tup))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("access method \"%s\" does not exist",
 						stmt->amname)));
 
 	amform = (Form_pg_am) GETSTRUCT(tup);
 	amoid = amform->oid;
-	amroutine = GetIndexAmRoutineByAmId(amoid, false);
+	amroutine = GetInterfaceAmRoutineByAmId(amoid);
 	ReleaseSysCache(tup);
 
 	maxOpNumber = amroutine->amstrategies;
 	/* if amstrategies is zero, just enforce that op numbers fit in int16 */
 	if (maxOpNumber <= 0)
 		maxOpNumber = SHRT_MAX;
 	maxProcNumber = amroutine->amsupport;
 	optsProcNumber = amroutine->amoptsprocnum;
 
 	/* XXX Should we make any privilege check against the AM? */
@@ -866,21 +867,21 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
 }
 
 /*
  * ADD part of ALTER OP FAMILY
  */
 static void
 AlterOpFamilyAdd(AlterOpFamilyStmt *stmt, Oid amoid, Oid opfamilyoid,
 				 int maxOpNumber, int maxProcNumber, int optsProcNumber,
 				 List *items)
 {
-	IndexAmRoutine *amroutine = GetIndexAmRoutineByAmId(amoid, false);
+	InterfaceAmRoutine *amroutine = GetInterfaceAmRoutineByAmId(amoid);
 	List	   *operators;		/* OpFamilyMember list for operators */
 	List	   *procedures;		/* OpFamilyMember list for support procs */
 	ListCell   *l;
 
 	operators = NIL;
 	procedures = NIL;
 
 	/*
 	 * Scan the "items" list to obtain additional info.
 	 */
@@ -1149,21 +1150,21 @@ assignOperTypes(OpFamilyMember *member, Oid amoid, Oid typeoid)
 		/*
 		 * Ordering op, check index supports that.  (We could perhaps also
 		 * check that the operator returns a type supported by the sortfamily,
 		 * but that seems more trouble than it's worth here.  If it does not,
 		 * the operator will never be matchable to any ORDER BY clause, but no
 		 * worse consequences can ensue.  Also, trying to check that would
 		 * create an ordering hazard during dump/reload: it's possible that
 		 * the family has been created but not yet populated with the required
 		 * operators.)
 		 */
-		IndexAmRoutine *amroutine = GetIndexAmRoutineByAmId(amoid, false);
+		InterfaceAmRoutine *amroutine = GetInterfaceAmRoutineByAmId(amoid);
 
 		if (!amroutine->amcanorderbyop)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 					 errmsg("access method \"%s\" does not support ordering operators",
 							get_am_name(amoid))));
 	}
 	else
 	{
 		/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 028e8ac46b..4f51cce331 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -16810,21 +16810,21 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
 		 * partitioning, we use a btree operator class; hash partitioning uses
 		 * a hash operator class.
 		 */
 		if (strategy == PARTITION_STRATEGY_HASH)
 			am_oid = HASH_AM_OID;
 		else
 			am_oid = BTREE_AM_OID;
 
 		if (!pelem->opclass)
 		{
-			partopclass[attn] = GetDefaultOpClass(atttype, am_oid);
+			partopclass[attn] = GetDefaultOpClass(atttype, list_make1_oid(am_oid));
 
 			if (!OidIsValid(partopclass[attn]))
 			{
 				if (strategy == PARTITION_STRATEGY_HASH)
 					ereport(ERROR,
 							(errcode(ERRCODE_UNDEFINED_OBJECT),
 							 errmsg("data type %s has no default operator class for access method \"%s\"",
 									format_type_be(atttype), "hash"),
 							 errhint("You must specify a hash operator class or define a default hash operator class for the data type.")));
 				else
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 58ec65c6af..bef0e6b511 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2302,21 +2302,21 @@ findRangeSubOpclass(List *opcname, Oid subtype)
 		opInputType = get_opclass_input_type(opcid);
 		if (!IsBinaryCoercible(subtype, opInputType))
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("operator class \"%s\" does not accept data type %s",
 							NameListToString(opcname),
 							format_type_be(subtype))));
 	}
 	else
 	{
-		opcid = GetDefaultOpClass(subtype, BTREE_AM_OID);
+		opcid = GetDefaultOpClass(subtype, list_make1_oid(BTREE_AM_OID));
 		if (!OidIsValid(opcid))
 		{
 			/* We spell the error message identically to ResolveOpClass */
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_OBJECT),
 					 errmsg("data type %s has no default operator class for access method \"%s\"",
 							format_type_be(subtype), "btree"),
 					 errhint("You must specify an operator class for the range type or define a default operator class for the subtype.")));
 		}
 	}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index bd87f23784..9a238a5f45 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4435,20 +4435,21 @@ _copyCreateTransformStmt(const CreateTransformStmt *from)
 }
 
 static CreateAmStmt *
 _copyCreateAmStmt(const CreateAmStmt *from)
 {
 	CreateAmStmt *newnode = makeNode(CreateAmStmt);
 
 	COPY_STRING_FIELD(amname);
 	COPY_NODE_FIELD(handler_name);
 	COPY_SCALAR_FIELD(amtype);
+	COPY_NODE_FIELD(implements);
 
 	return newnode;
 }
 
 static CreateTrigStmt *
 _copyCreateTrigStmt(const CreateTrigStmt *from)
 {
 	CreateTrigStmt *newnode = makeNode(CreateTrigStmt);
 
 	COPY_SCALAR_FIELD(replace);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index dba3e6b31e..c543fe8dc4 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2044,20 +2044,21 @@ _equalCreateTransformStmt(const CreateTransformStmt *a, const CreateTransformStm
 
 	return true;
 }
 
 static bool
 _equalCreateAmStmt(const CreateAmStmt *a, const CreateAmStmt *b)
 {
 	COMPARE_STRING_FIELD(amname);
 	COMPARE_NODE_FIELD(handler_name);
 	COMPARE_SCALAR_FIELD(amtype);
+	COMPARE_NODE_FIELD(implements);
 
 	return true;
 }
 
 static bool
 _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 {
 	COMPARE_SCALAR_FIELD(replace);
 	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_STRING_FIELD(trigname);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index eb24195438..a0a3a9d8f8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -415,20 +415,21 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				target_list opt_target_list insert_column_list set_target_list
 				set_clause_list set_clause
 				def_list operator_def_list indirection opt_indirection
 				reloption_list TriggerFuncArgs opclass_item_list opclass_drop_list
 				opclass_purpose opt_opfamily transaction_mode_list_or_empty
 				OptTableFuncElementList TableFuncElementList opt_type_modifiers
 				prep_type_clause
 				execute_param_clause using_clause returning_clause
 				opt_enum_val_list enum_val_list table_func_column_list
 				create_generic_options alter_generic_options
+				OptImplement
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
 				TriggerTransitions TriggerReferencing
 				vacuum_relation_list opt_vacuum_relation_list
 				drop_option_list
 
 %type <node>	opt_routine_body
 %type <groupclause> group_clause
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -661,24 +662,25 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION
 	EXTENSION EXTERNAL EXTRACT
 
 	FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR
 	FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
 
 	GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS
 
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
-	IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE
+	IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE
+	IMPLEMENTS IMPLICIT_P IMPORT_P IN_P INCLUDE
 	INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
 	INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
-	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
+	INTERFACE INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
 	JOIN
 
 	KEY
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
 	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
 	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
 
 	MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -5316,34 +5318,41 @@ row_security_cmd:
 		;
 
 /*****************************************************************************
  *
  *		QUERY:
  *             CREATE ACCESS METHOD name HANDLER handler_name
  *
  *****************************************************************************/
 
 CreateAmStmt: CREATE ACCESS METHOD name TYPE_P am_type HANDLER handler_name
+			  OptImplement
 				{
 					CreateAmStmt *n = makeNode(CreateAmStmt);
 					n->amname = $4;
 					n->handler_name = $8;
 					n->amtype = $6;
+					n->implements = $9;
 					$$ = (Node *) n;
 				}
 		;
 
 am_type:
-			INDEX			{ $$ = AMTYPE_INDEX; }
+			INTERFACE		{ $$ = AMTYPE_INTERFACE; }
+		|	INDEX			{ $$ = AMTYPE_INDEX; }
 		|	TABLE			{ $$ = AMTYPE_TABLE; }
 		;
 
+OptImplement: IMPLEMENTS '(' name_list ')'	{ $$ = $3; }
+			  | /*EMPTY*/					{ $$ = NIL; }
+			  ;
+
 /*****************************************************************************
  *
  *		QUERIES :
  *				CREATE TRIGGER ...
  *
  *****************************************************************************/
 
 CreateTrigStmt:
 			CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON
 			qualified_name TriggerReferencing TriggerForSpec TriggerWhen
@@ -15572,34 +15581,36 @@ unreserved_keyword:
 			| GRANTED
 			| GROUPS
 			| HANDLER
 			| HEADER_P
 			| HOLD
 			| HOUR_P
 			| IDENTITY_P
 			| IF_P
 			| IMMEDIATE
 			| IMMUTABLE
+			| IMPLEMENTS
 			| IMPLICIT_P
 			| IMPORT_P
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
 			| INDEX
 			| INDEXES
 			| INHERIT
 			| INHERITS
 			| INLINE_P
 			| INPUT_P
 			| INSENSITIVE
 			| INSERT
 			| INSTEAD
+			| INTERFACE
 			| INVOKER
 			| ISOLATION
 			| KEY
 			| LABEL
 			| LANGUAGE
 			| LARGE_P
 			| LAST_P
 			| LEAKPROOF
 			| LEVEL
 			| LISTEN
@@ -16123,40 +16134,42 @@ bare_label_keyword:
 			| GROUPING
 			| GROUPS
 			| HANDLER
 			| HEADER_P
 			| HOLD
 			| IDENTITY_P
 			| IF_P
 			| ILIKE
 			| IMMEDIATE
 			| IMMUTABLE
+			| IMPLEMENTS
 			| IMPLICIT_P
 			| IMPORT_P
 			| IN_P
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
 			| INDEX
 			| INDEXES
 			| INHERIT
 			| INHERITS
 			| INITIALLY
 			| INLINE_P
 			| INNER_P
 			| INOUT
 			| INPUT_P
 			| INSENSITIVE
 			| INSERT
 			| INSTEAD
 			| INT_P
 			| INTEGER
+			| INTERFACE
 			| INTERVAL
 			| INVOKER
 			| IS
 			| ISOLATION
 			| JOIN
 			| KEY
 			| LABEL
 			| LANGUAGE
 			| LARGE_P
 			| LAST_P
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index c9708e38f4..7f1f62fa6a 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -2034,21 +2034,21 @@ get_opclass(Oid opclass, Oid actual_datatype)
 {
 	List	   *result = NIL;
 	HeapTuple	ht_opc;
 	Form_pg_opclass opc_rec;
 
 	ht_opc = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
 	if (!HeapTupleIsValid(ht_opc))
 		elog(ERROR, "cache lookup failed for opclass %u", opclass);
 	opc_rec = (Form_pg_opclass) GETSTRUCT(ht_opc);
 
-	if (GetDefaultOpClass(actual_datatype, opc_rec->opcmethod) != opclass)
+	if (GetDefaultOpClass(actual_datatype, list_make1_oid(opc_rec->opcmethod)) != opclass)
 	{
 		/* For simplicity, we always schema-qualify the name */
 		char	   *nsp_name = get_namespace_name(opc_rec->opcnamespace);
 		char	   *opc_name = pstrdup(NameStr(opc_rec->opcname));
 
 		result = list_make2(makeString(nsp_name), makeString(opc_name));
 	}
 
 	ReleaseSysCache(ht_opc);
 	return result;
@@ -2316,21 +2316,21 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 					 errdetail("Cannot create a non-deferrable constraint using a deferrable index."),
 					 parser_errposition(cxt->pstate, constraint->location)));
 
 		/*
 		 * Insist on it being a btree.  That's the only kind that supports
 		 * uniqueness at the moment anyway; but we must have an index that
 		 * exactly matches what you'd get from plain ADD CONSTRAINT syntax,
 		 * else dump and reload will produce a different index (breaking
 		 * pg_upgrade in particular).
 		 */
-		if (index_rel->rd_rel->relam != get_index_am_oid(DEFAULT_INDEX_TYPE, false))
+		if (index_rel->rd_rel->relam != BTREE_AM_OID)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("index \"%s\" is not a btree", index_name),
 					 parser_errposition(cxt->pstate, constraint->location)));
 
 		/* Must get indclass the hard way */
 		indclassDatum = SysCacheGetAttr(INDEXRELID, index_rel->rd_indextuple,
 										Anum_pg_index_indclass, &isnull);
 		Assert(!isnull);
 		indclass = (oidvector *) DatumGetPointer(indclassDatum);
@@ -2363,21 +2363,21 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 				 * While the index would still work as a constraint with
 				 * non-default settings, it might not provide exactly the same
 				 * uniqueness semantics as you'd get from a normally-created
 				 * constraint; and there's also the dump/reload problem
 				 * mentioned above.
 				 */
 				Datum		attoptions =
 				get_attoptions(RelationGetRelid(index_rel), i + 1);
 
 				defopclass = GetDefaultOpClass(attform->atttypid,
-											   index_rel->rd_rel->relam);
+											   list_make1_oid(index_rel->rd_rel->relam));
 				if (indclass->values[i] != defopclass ||
 					attform->attcollation != index_rel->rd_indcollation[i] ||
 					attoptions != (Datum) 0 ||
 					index_rel->rd_indoption[i] != 0)
 					ereport(ERROR,
 							(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 							 errmsg("index \"%s\" column number %d does not have default sorting behavior", index_name, i + 1),
 							 errdetail("Cannot create a primary key or unique constraint using such an index."),
 							 parser_errposition(cxt->pstate, constraint->location)));
 
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index c2f910d606..2a2095bdf1 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -375,17 +375,18 @@ PSEUDOTYPE_DUMMY_BINARY_IO_FUNCS(pg_ddl_command);
 
 /*
  * Dummy I/O functions for various other pseudotypes.
  */
 PSEUDOTYPE_DUMMY_IO_FUNCS(any);
 PSEUDOTYPE_DUMMY_IO_FUNCS(trigger);
 PSEUDOTYPE_DUMMY_IO_FUNCS(event_trigger);
 PSEUDOTYPE_DUMMY_IO_FUNCS(language_handler);
 PSEUDOTYPE_DUMMY_IO_FUNCS(fdw_handler);
 PSEUDOTYPE_DUMMY_IO_FUNCS(table_am_handler);
+PSEUDOTYPE_DUMMY_IO_FUNCS(interface_am_handler);
 PSEUDOTYPE_DUMMY_IO_FUNCS(index_am_handler);
 PSEUDOTYPE_DUMMY_IO_FUNCS(tsm_handler);
 PSEUDOTYPE_DUMMY_IO_FUNCS(internal);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anyelement);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anynonarray);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anycompatible);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anycompatiblenonarray);
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3719755a0d..ab72cff899 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -11168,21 +11168,21 @@ get_opclass_name(Oid opclass, Oid actual_datatype,
 	Form_pg_opclass opcrec;
 	char	   *opcname;
 	char	   *nspname;
 
 	ht_opc = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
 	if (!HeapTupleIsValid(ht_opc))
 		elog(ERROR, "cache lookup failed for opclass %u", opclass);
 	opcrec = (Form_pg_opclass) GETSTRUCT(ht_opc);
 
 	if (!OidIsValid(actual_datatype) ||
-		GetDefaultOpClass(actual_datatype, opcrec->opcmethod) != opclass)
+		GetDefaultOpClass(actual_datatype, list_make1_oid(opcrec->opcmethod)) != opclass)
 	{
 		/* Okay, we need the opclass name.  Do we need to qualify it? */
 		opcname = NameStr(opcrec->opcname);
 		if (OpclassIsVisible(opclass))
 			appendStringInfo(buf, " %s", quote_identifier(opcname));
 		else
 		{
 			nspname = get_namespace_name(opcrec->opcnamespace);
 			appendStringInfo(buf, " %s.%s",
 							 quote_identifier(nspname),
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index de96e96c8f..ed6630f263 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -467,21 +467,21 @@ lookup_type_cache(Oid type_id, int flags)
 	 * requested.
 	 */
 	if ((flags & (TYPECACHE_EQ_OPR | TYPECACHE_LT_OPR | TYPECACHE_GT_OPR |
 				  TYPECACHE_CMP_PROC |
 				  TYPECACHE_EQ_OPR_FINFO | TYPECACHE_CMP_PROC_FINFO |
 				  TYPECACHE_BTREE_OPFAMILY)) &&
 		!(typentry->flags & TCFLAGS_CHECKED_BTREE_OPCLASS))
 	{
 		Oid			opclass;
 
-		opclass = GetDefaultOpClass(type_id, BTREE_AM_OID);
+		opclass = GetDefaultOpClass(type_id, list_make1_oid(BTREE_AM_OID));
 		if (OidIsValid(opclass))
 		{
 			typentry->btree_opf = get_opclass_family(opclass);
 			typentry->btree_opintype = get_opclass_input_type(opclass);
 		}
 		else
 		{
 			typentry->btree_opf = typentry->btree_opintype = InvalidOid;
 		}
 
@@ -508,21 +508,21 @@ lookup_type_cache(Oid type_id, int flags)
 		flags |= TYPECACHE_HASH_OPFAMILY;
 
 	if ((flags & (TYPECACHE_HASH_PROC | TYPECACHE_HASH_PROC_FINFO |
 				  TYPECACHE_HASH_EXTENDED_PROC |
 				  TYPECACHE_HASH_EXTENDED_PROC_FINFO |
 				  TYPECACHE_HASH_OPFAMILY)) &&
 		!(typentry->flags & TCFLAGS_CHECKED_HASH_OPCLASS))
 	{
 		Oid			opclass;
 
-		opclass = GetDefaultOpClass(type_id, HASH_AM_OID);
+		opclass = GetDefaultOpClass(type_id, list_make1_oid(HASH_AM_OID));
 		if (OidIsValid(opclass))
 		{
 			typentry->hash_opf = get_opclass_family(opclass);
 			typentry->hash_opintype = get_opclass_input_type(opclass);
 		}
 		else
 		{
 			typentry->hash_opf = typentry->hash_opintype = InvalidOid;
 		}
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8f53cc7c3b..7ee1d3ef07 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -13075,20 +13075,23 @@ dumpAccessMethod(Archive *fout, const AccessMethodInfo *aminfo)
 
 	q = createPQExpBuffer();
 	delq = createPQExpBuffer();
 
 	qamname = pg_strdup(fmtId(aminfo->dobj.name));
 
 	appendPQExpBuffer(q, "CREATE ACCESS METHOD %s ", qamname);
 
 	switch (aminfo->amtype)
 	{
+		case AMTYPE_INTERFACE:
+			appendPQExpBufferStr(q, "TYPE INTERFACE ");
+			break;
 		case AMTYPE_INDEX:
 			appendPQExpBufferStr(q, "TYPE INDEX ");
 			break;
 		case AMTYPE_TABLE:
 			appendPQExpBufferStr(q, "TYPE TABLE ");
 			break;
 		default:
 			pg_log_warning("invalid type \"%c\" of access method \"%s\"",
 						   aminfo->amtype, qamname);
 			destroyPQExpBuffer(q);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 2abf255798..9764194109 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -162,24 +162,26 @@ describeAccessMethods(const char *pattern, bool verbose)
 					 formatPGVersionNumber(pset.sversion, false,
 										   sverbuf, sizeof(sverbuf)));
 		return true;
 	}
 
 	initPQExpBuffer(&buf);
 
 	printfPQExpBuffer(&buf,
 					  "SELECT amname AS \"%s\",\n"
 					  "  CASE amtype"
+					  " WHEN 'n' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
 					  " WHEN 't' THEN '%s'"
 					  " END AS \"%s\"",
 					  gettext_noop("Name"),
+					  gettext_noop("Interface"),
 					  gettext_noop("Index"),
 					  gettext_noop("Table"),
 					  gettext_noop("Type"));
 
 	if (verbose)
 	{
 		appendPQExpBuffer(&buf,
 						  ",\n  amhandler AS \"%s\",\n"
 						  "  pg_catalog.obj_description(oid, 'pg_am') AS \"%s\"",
 						  gettext_noop("Handler"),
diff --git a/src/include/access/ifam.h b/src/include/access/ifam.h
new file mode 100644
index 0000000000..c6bdc17667
--- /dev/null
+++ b/src/include/access/ifam.h
@@ -0,0 +1,64 @@
+/*-------------------------------------------------------------------------
+ *
+ * ifam.h
+ *	  API for Postgres interface access methods
+ *
+ * Copyright (c) 2015-2021, PostgreSQL Global Development Group
+ *
+ * src/include/access/ifam.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef IFAM_H
+#define IFAM_H
+
+#include "access/genam.h"
+
+/* validate definition of an opclass for this AM */
+typedef bool (*amvalidate_function) (Oid opclassoid);
+
+/* validate operators and support functions to be added to an opclass/family */
+typedef void (*amadjustmembers_function) (Oid opfamilyoid,
+										  Oid opclassoid,
+										  List *operators,
+										  List *functions);
+
+/*
+ * API struct for an index AM.  Note this must be stored in a single palloc'd
+ * chunk of memory.
+ */
+typedef struct InterfaceAmRoutine
+{
+	NodeTag		type;
+
+	/*
+	 * Total number of strategies (operators) by which we can traverse/search
+	 * this AM.  Zero if AM does not have a fixed set of strategy assignments.
+	 */
+	uint16		amstrategies;
+	/* total number of support functions that this AM uses */
+	uint16		amsupport;
+	/* opclass options support function number or 0 */
+	uint16		amoptsprocnum;
+	/* does AM support ORDER BY indexed column's value? */
+	bool		amcanorder;
+	/* does AM support ORDER BY result of an operator on indexed column? */
+	bool		amcanorderbyop;
+	/* can index storage data type differ from column data type? */
+	bool		amstorage;
+
+	/*
+	 * If you add new properties to either the above or the below lists, then
+	 * they should also (usually) be exposed via the property API (see
+	 * InterfaceAMProperty at the top of the file, and utils/adt/amutils.c).
+	 */
+
+	/* interface functions */
+	amvalidate_function amvalidate;
+	amadjustmembers_function amadjustmembers;	/* can be NULL */
+} InterfaceAmRoutine;
+
+/* Functions in access/interface/ifamapi.c */
+extern InterfaceAmRoutine *GetInterfaceAmRoutineByAmId(Oid amoid);
+
+#endif							/* IFAM_H */
diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat
index 6082f0e6a8..f30c8d1aa0 100644
--- a/src/include/catalog/pg_am.dat
+++ b/src/include/catalog/pg_am.dat
@@ -8,20 +8,23 @@
 #
 # src/include/catalog/pg_am.dat
 #
 #----------------------------------------------------------------------
 
 [
 
 { oid => '2', oid_symbol => 'HEAP_TABLE_AM_OID',
   descr => 'heap table access method',
   amname => 'heap', amhandler => 'heap_tableam_handler', amtype => 't' },
+{ oid => '4', oid_symbol => 'ORDERING_AM_OID',
+  descr => 'ordering interface',
+  amname => 'ordering', amhandler => 'ordering_ifam_handler', amtype => 'n' },
 { oid => '403', oid_symbol => 'BTREE_AM_OID',
   descr => 'b-tree index access method',
   amname => 'btree', amhandler => 'bthandler', amtype => 'i' },
 { oid => '405', oid_symbol => 'HASH_AM_OID',
   descr => 'hash index access method',
   amname => 'hash', amhandler => 'hashhandler', amtype => 'i' },
 { oid => '783', oid_symbol => 'GIST_AM_OID',
   descr => 'GiST index access method',
   amname => 'gist', amhandler => 'gisthandler', amtype => 'i' },
 { oid => '2742', oid_symbol => 'GIN_AM_OID',
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index ced86faef8..5fee05c95f 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -50,16 +50,17 @@ typedef FormData_pg_am *Form_pg_am;
 DECLARE_UNIQUE_INDEX(pg_am_name_index, 2651, on pg_am using btree(amname name_ops));
 #define AmNameIndexId  2651
 DECLARE_UNIQUE_INDEX_PKEY(pg_am_oid_index, 2652, on pg_am using btree(oid oid_ops));
 #define AmOidIndexId  2652
 
 #ifdef EXPOSE_TO_CLIENT_CODE
 
 /*
  * Allowed values for amtype
  */
+#define AMTYPE_INTERFACE				'n' /* interface access method */
 #define AMTYPE_INDEX					'i' /* index access method */
 #define AMTYPE_TABLE					't' /* table access method */
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
 #endif							/* PG_AM_H */
diff --git a/src/include/catalog/pg_amimplements.dat b/src/include/catalog/pg_amimplements.dat
new file mode 100644
index 0000000000..298617b526
--- /dev/null
+++ b/src/include/catalog/pg_amimplements.dat
@@ -0,0 +1,17 @@
+#----------------------------------------------------------------------
+#
+# pg_amimplements.dat
+#    Initial contents of the pg_amimplements system catalog.
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/include/catalog/pg_amimplements.dat
+#
+#----------------------------------------------------------------------
+
+[
+
+{ amiamid => 'btree', amiparent => 'ordering', amiseqno => '1' },
+
+]
diff --git a/src/include/catalog/pg_amimplements.h b/src/include/catalog/pg_amimplements.h
new file mode 100644
index 0000000000..246b625595
--- /dev/null
+++ b/src/include/catalog/pg_amimplements.h
@@ -0,0 +1,51 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_amimplements.h
+ *	  definition of the "implements" system catalog (pg_amimplements)
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_amimplements.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_AMIMPLEMENTS_H
+#define PG_AMIMPLEMENTS_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_amimplements_d.h"
+
+#include "nodes/pg_list.h"
+#include "storage/lock.h"
+
+/* ----------------
+ *		pg_amimplements definition.  cpp turns this into
+ *		typedef struct FormData_pg_amimplements
+ * ----------------
+ */
+CATALOG(pg_amimplements,4544,AmimplementsRelationId)
+{
+	Oid			amiamid BKI_LOOKUP(pg_am);
+	Oid			amiparent BKI_LOOKUP(pg_am);
+	int32		amiseqno;
+} FormData_pg_amimplements;
+
+/* ----------------
+ *		Form_pg_amimplements corresponds to a pointer to a tuple with
+ *		the format of pg_amimplements relation.
+ * ----------------
+ */
+typedef FormData_pg_amimplements *Form_pg_amimplements;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_amimplements_amid_seqno_index, 4545, on pg_amimplements using btree(amiamid oid_ops, amiseqno int4_ops));
+#define AmimplementsAmidSeqnoIndexId	4545
+DECLARE_INDEX(pg_amimplements_parent_index, 4546, on pg_amimplements using btree(amiparent oid_ops));
+#define AmimplementsParentIndexId	4546
+
+#endif							/* PG_AMIMPLEMENTS_H */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index acbcae4607..02c128e6c0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -899,20 +899,26 @@
 { oid => '319', descr => 'convert float4 to int4',
   proname => 'int4', prorettype => 'int4', proargtypes => 'float4',
   prosrc => 'ftoi4' },
 
 # Table access method handlers
 { oid => '3', descr => 'row-oriented heap table access method handler',
   proname => 'heap_tableam_handler', provolatile => 'v',
   prorettype => 'table_am_handler', proargtypes => 'internal',
   prosrc => 'heap_tableam_handler' },
 
+# Interface access method handlers
+{ oid => '5', descr => 'ordering interface access method handler',
+  proname => 'ordering_ifam_handler', provolatile => 'v',
+  prorettype => 'interface_am_handler', proargtypes => 'internal',
+  prosrc => 'ordering_ifam_handler' },
+
 # Index access method handlers
 { oid => '330', descr => 'btree index access method handler',
   proname => 'bthandler', provolatile => 'v', prorettype => 'index_am_handler',
   proargtypes => 'internal', prosrc => 'bthandler' },
 { oid => '331', descr => 'hash index access method handler',
   proname => 'hashhandler', provolatile => 'v',
   prorettype => 'index_am_handler', proargtypes => 'internal',
   prosrc => 'hashhandler' },
 { oid => '332', descr => 'gist index access method handler',
   proname => 'gisthandler', provolatile => 'v',
@@ -7298,20 +7304,27 @@
   proargtypes => 'cstring', prosrc => 'anynonarray_in' },
 { oid => '2778', descr => 'I/O',
   proname => 'anynonarray_out', prorettype => 'cstring',
   proargtypes => 'anynonarray', prosrc => 'anynonarray_out' },
 { oid => '3116', descr => 'I/O',
   proname => 'fdw_handler_in', proisstrict => 'f', prorettype => 'fdw_handler',
   proargtypes => 'cstring', prosrc => 'fdw_handler_in' },
 { oid => '3117', descr => 'I/O',
   proname => 'fdw_handler_out', prorettype => 'cstring',
   proargtypes => 'fdw_handler', prosrc => 'fdw_handler_out' },
+{ oid => '561', descr => 'I/O',
+  proname => 'interface_am_handler_in', proisstrict => 'f',
+  prorettype => 'interface_am_handler', proargtypes => 'cstring',
+  prosrc => 'interface_am_handler_in' },
+{ oid => '562', descr => 'I/O',
+  proname => 'interface_am_handler_out', prorettype => 'cstring',
+  proargtypes => 'interface_am_handler', prosrc => 'interface_am_handler_out' },
 { oid => '326', descr => 'I/O',
   proname => 'index_am_handler_in', proisstrict => 'f',
   prorettype => 'index_am_handler', proargtypes => 'cstring',
   prosrc => 'index_am_handler_in' },
 { oid => '327', descr => 'I/O',
   proname => 'index_am_handler_out', prorettype => 'cstring',
   proargtypes => 'index_am_handler', prosrc => 'index_am_handler_out' },
 { oid => '3311', descr => 'I/O',
   proname => 'tsm_handler_in', proisstrict => 'f', prorettype => 'tsm_handler',
   proargtypes => 'cstring', prosrc => 'tsm_handler_in' },
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index 41074c994b..acaab8e0f8 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -613,20 +613,26 @@
   descr => 'pseudo-type representing a polymorphic base type that is an enum',
   typname => 'anyenum', typlen => '4', typbyval => 't', typtype => 'p',
   typcategory => 'P', typinput => 'anyenum_in', typoutput => 'anyenum_out',
   typreceive => '-', typsend => '-', typalign => 'i' },
 { oid => '3115',
   descr => 'pseudo-type for the result of an FDW handler function',
   typname => 'fdw_handler', typlen => '4', typbyval => 't', typtype => 'p',
   typcategory => 'P', typinput => 'fdw_handler_in',
   typoutput => 'fdw_handler_out', typreceive => '-', typsend => '-',
   typalign => 'i' },
+{ oid => '560',
+  descr => 'pseudo-type for the result of an interface AM handler function',
+  typname => 'interface_am_handler', typlen => '4', typbyval => 't', typtype => 'p',
+  typcategory => 'P', typinput => 'interface_am_handler_in',
+  typoutput => 'interface_am_handler_out', typreceive => '-', typsend => '-',
+  typalign => 'i' },
 { oid => '325',
   descr => 'pseudo-type for the result of an index AM handler function',
   typname => 'index_am_handler', typlen => '4', typbyval => 't', typtype => 'p',
   typcategory => 'P', typinput => 'index_am_handler_in',
   typoutput => 'index_am_handler_out', typreceive => '-', typsend => '-',
   typalign => 'i' },
 { oid => '3310',
   descr => 'pseudo-type for the result of a tablesample method function',
   typname => 'tsm_handler', typlen => '4', typbyval => 't', typtype => 'p',
   typcategory => 'P', typinput => 'tsm_handler_in',
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 42bf1c7519..32715b8fa6 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -37,21 +37,21 @@ extern ObjectAddress DefineIndex(Oid relationId,
 extern void ExecReindex(ParseState *pstate, ReindexStmt *stmt, bool isTopLevel);
 extern char *makeObjectName(const char *name1, const char *name2,
 							const char *label);
 extern char *ChooseRelationName(const char *name1, const char *name2,
 								const char *label, Oid namespaceid,
 								bool isconstraint);
 extern bool CheckIndexCompatible(Oid oldId,
 								 const char *accessMethodName,
 								 List *attributeList,
 								 List *exclusionOpNames);
-extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	GetDefaultOpClass(Oid type_id, List *am_ids);
 extern Oid	ResolveOpClass(List *opclass, Oid attrType,
 						   const char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
 extern void RemoveFunctionById(Oid funcOid);
 extern ObjectAddress AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt);
 extern ObjectAddress CreateCast(CreateCastStmt *stmt);
 extern ObjectAddress CreateTransform(CreateTransformStmt *stmt);
 extern void IsThereFunctionInNamespace(const char *proname, int pronargs,
@@ -130,21 +130,21 @@ extern ObjectAddress AlterUserMapping(AlterUserMappingStmt *stmt);
 extern Oid	RemoveUserMapping(DropUserMappingStmt *stmt);
 extern void CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid);
 extern void ImportForeignSchema(ImportForeignSchemaStmt *stmt);
 extern Datum transformGenericOptions(Oid catalogId,
 									 Datum oldOptions,
 									 List *options,
 									 Oid fdwvalidator);
 
 /* commands/amcmds.c */
 extern ObjectAddress CreateAccessMethod(CreateAmStmt *stmt);
-extern Oid	get_index_am_oid(const char *amname, bool missing_ok);
+extern Oid	get_interface_or_index_am_oid(const char *amname, bool missing_ok);
 extern Oid	get_table_am_oid(const char *amname, bool missing_ok);
 extern Oid	get_am_oid(const char *amname, bool missing_ok);
 extern char *get_am_name(Oid amOid);
 
 /* support routines in commands/define.c */
 
 extern char *defGetString(DefElem *def);
 extern double defGetNumeric(DefElem *def);
 extern bool defGetBoolean(DefElem *def);
 extern int32 defGetInt32(DefElem *def);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index d9e417bcd7..6e8529997e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -511,20 +511,21 @@ typedef enum NodeTag
 	 * purposes (usually because they are involved in APIs where we want to
 	 * pass multiple object types through the same pointer).
 	 */
 	T_TriggerData,				/* in commands/trigger.h */
 	T_EventTriggerData,			/* in commands/event_trigger.h */
 	T_ReturnSetInfo,			/* in nodes/execnodes.h */
 	T_WindowObjectData,			/* private in nodeWindowAgg.c */
 	T_TIDBitmap,				/* in nodes/tidbitmap.h */
 	T_InlineCodeBlock,			/* in nodes/parsenodes.h */
 	T_FdwRoutine,				/* in foreign/fdwapi.h */
+	T_InterfaceAmRoutine,		/* in access/ifam.h */
 	T_IndexAmRoutine,			/* in access/amapi.h */
 	T_TableAmRoutine,			/* in access/tableam.h */
 	T_TsmRoutine,				/* in access/tsmapi.h */
 	T_ForeignKeyCacheInfo,		/* in utils/rel.h */
 	T_CallContext,				/* in nodes/parsenodes.h */
 	T_SupportRequestSimplify,	/* in nodes/supportnodes.h */
 	T_SupportRequestSelectivity,	/* in nodes/supportnodes.h */
 	T_SupportRequestCost,		/* in nodes/supportnodes.h */
 	T_SupportRequestRows,		/* in nodes/supportnodes.h */
 	T_SupportRequestIndexCondition	/* in nodes/supportnodes.h */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index def9651b34..8c639d892a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2509,20 +2509,21 @@ typedef struct AlterPolicyStmt
 /*----------------------
  *		Create ACCESS METHOD Statement
  *----------------------
  */
 typedef struct CreateAmStmt
 {
 	NodeTag		type;
 	char	   *amname;			/* access method name */
 	List	   *handler_name;	/* handler function name */
 	char		amtype;			/* type of access method */
+	List	   *implements;		/* implemented interface access methods */
 } CreateAmStmt;
 
 /* ----------------------
  *		Create TRIGGER Statement
  * ----------------------
  */
 typedef struct CreateTrigStmt
 {
 	NodeTag		type;
 	bool		replace;		/* replace trigger if already exists */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf876..b5cb568d74 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -192,40 +192,42 @@ PG_KEYWORD("groups", GROUPS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("handler", HANDLER, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("having", HAVING, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("header", HEADER_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("hold", HOLD, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("hour", HOUR_P, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("identity", IDENTITY_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("if", IF_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("ilike", ILIKE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("immediate", IMMEDIATE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("immutable", IMMUTABLE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("implements", IMPLEMENTS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("in", IN_P, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("inherits", INHERITS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("initially", INITIALLY, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("inline", INLINE_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("inner", INNER_P, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("inout", INOUT, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("input", INPUT_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("insensitive", INSENSITIVE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("insert", INSERT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("instead", INSTEAD, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("int", INT_P, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("integer", INTEGER, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("interface", INTERFACE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("intersect", INTERSECT, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("interval", INTERVAL, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("into", INTO, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("invoker", INVOKER, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD, AS_LABEL)
 PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/test/regress/expected/amutils.out b/src/test/regress/expected/amutils.out
index 7ab6113c61..0b5381a65a 100644
--- a/src/test/regress/expected/amutils.out
+++ b/src/test/regress/expected/amutils.out
@@ -142,20 +142,26 @@ select amname, prop, pg_indexam_has_property(a.oid, prop) as p
  brin   | can_multi_col | t
  brin   | can_exclude   | f
  brin   | can_include   | f
  brin   | bogus         | 
  btree  | can_order     | t
  btree  | can_unique    | t
  btree  | can_multi_col | t
  btree  | can_exclude   | t
  btree  | can_include   | t
  btree  | bogus         | 
+ btree2 | can_order     | t
+ btree2 | can_unique    | t
+ btree2 | can_multi_col | t
+ btree2 | can_exclude   | t
+ btree2 | can_include   | t
+ btree2 | bogus         | 
  gin    | can_order     | f
  gin    | can_unique    | f
  gin    | can_multi_col | t
  gin    | can_exclude   | f
  gin    | can_include   | f
  gin    | bogus         | 
  gist   | can_order     | f
  gist   | can_unique    | f
  gist   | can_multi_col | t
  gist   | can_exclude   | t
@@ -166,21 +172,21 @@ select amname, prop, pg_indexam_has_property(a.oid, prop) as p
  hash   | can_multi_col | f
  hash   | can_exclude   | t
  hash   | can_include   | f
  hash   | bogus         | 
  spgist | can_order     | f
  spgist | can_unique    | f
  spgist | can_multi_col | f
  spgist | can_exclude   | t
  spgist | can_include   | t
  spgist | bogus         | 
-(36 rows)
+(42 rows)
 
 --
 -- additional checks for pg_index_column_has_property
 --
 CREATE TEMP TABLE foo (f1 int, f2 int, f3 int, f4 int);
 CREATE INDEX fooindex ON foo (f1 desc, f2 asc, f3 nulls first, f4 nulls last);
 select col, prop, pg_index_column_has_property(o, col, prop)
   from (values ('fooindex'::regclass)) v1(o),
        (values (1,'orderable'),(2,'asc'),(3,'desc'),
                (4,'nulls_first'),(5,'nulls_last'),
diff --git a/src/test/regress/expected/create_am.out b/src/test/regress/expected/create_am.out
index 0dfb26c301..c662f613c1 100644
--- a/src/test/regress/expected/create_am.out
+++ b/src/test/regress/expected/create_am.out
@@ -1,15 +1,25 @@
 --
 -- Create access method tests
 --
--- Make gist2 over gisthandler. In fact, it would be a synonym to gist.
+-- Add synonyms to btree and gist
+CREATE ACCESS METHOD btree2 TYPE INDEX HANDLER bthandler IMPLEMENTS (ordering);
 CREATE ACCESS METHOD gist2 TYPE INDEX HANDLER gisthandler;
+-- Verify IMPLEMENTS
+CREATE ACCESS METHOD bogus TYPE INTERFACE HANDLER heap_tableam_handler IMPLEMENTS (ordering);
+ERROR:  function heap_tableam_handler must return type interface_am_handler
+CREATE ACCESS METHOD bogus TYPE TABLE HANDLER heap_tableam_handler IMPLEMENTS (ordering);
+ERROR:  only index access methods can implement interfaces
+CREATE ACCESS METHOD bogus TYPE INDEX HANDLER bthandler IMPLEMENTS (ordering, ordering);
+ERROR:  access method "ordering" would be implemented more than once
+CREATE ACCESS METHOD bogus TYPE INDEX HANDLER bthandler IMPLEMENTS (heap);
+ERROR:  access method "heap" is not of type INTERFACE
 -- Verify return type checks for handlers
 CREATE ACCESS METHOD bogus TYPE INDEX HANDLER int4in;
 ERROR:  function int4in(internal) does not exist
 CREATE ACCESS METHOD bogus TYPE INDEX HANDLER heap_tableam_handler;
 ERROR:  function heap_tableam_handler must return type index_am_handler
 -- Try to create gist2 index on fast_emp4000: fail because opclass doesn't exist
 CREATE INDEX grect2ind2 ON fast_emp4000 USING gist2 (home_base);
 ERROR:  data type box has no default operator class for access method "gist2"
 HINT:  You must specify an operator class for the index or define a default operator class for the data type.
 -- Make operator class for boxes using gist2
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..e90ff0747a 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -116,20 +116,22 @@ NOTICE:  checking pg_operator {oprjoin} => pg_proc {oid}
 NOTICE:  checking pg_opfamily {opfmethod} => pg_am {oid}
 NOTICE:  checking pg_opfamily {opfnamespace} => pg_namespace {oid}
 NOTICE:  checking pg_opfamily {opfowner} => pg_authid {oid}
 NOTICE:  checking pg_opclass {opcmethod} => pg_am {oid}
 NOTICE:  checking pg_opclass {opcnamespace} => pg_namespace {oid}
 NOTICE:  checking pg_opclass {opcowner} => pg_authid {oid}
 NOTICE:  checking pg_opclass {opcfamily} => pg_opfamily {oid}
 NOTICE:  checking pg_opclass {opcintype} => pg_type {oid}
 NOTICE:  checking pg_opclass {opckeytype} => pg_type {oid}
 NOTICE:  checking pg_am {amhandler} => pg_proc {oid}
+NOTICE:  checking pg_amimplements {amiamid} => pg_am {oid}
+NOTICE:  checking pg_amimplements {amiparent} => pg_am {oid}
 NOTICE:  checking pg_amop {amopfamily} => pg_opfamily {oid}
 NOTICE:  checking pg_amop {amoplefttype} => pg_type {oid}
 NOTICE:  checking pg_amop {amoprighttype} => pg_type {oid}
 NOTICE:  checking pg_amop {amopopr} => pg_operator {oid}
 NOTICE:  checking pg_amop {amopmethod} => pg_am {oid}
 NOTICE:  checking pg_amop {amopsortfamily} => pg_opfamily {oid}
 NOTICE:  checking pg_amproc {amprocfamily} => pg_opfamily {oid}
 NOTICE:  checking pg_amproc {amproclefttype} => pg_type {oid}
 NOTICE:  checking pg_amproc {amprocrighttype} => pg_type {oid}
 NOTICE:  checking pg_amproc {amproc} => pg_proc {oid}
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 1b2f6bc418..c13d59b15c 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4891,45 +4891,49 @@ TOAST table "pg_toast.pg_toast_2619"
  chunk_id   | oid
  chunk_seq  | integer
  chunk_data | bytea
 Owning table: "pg_catalog.pg_statistic"
 Indexes:
     "pg_toast_2619_index" PRIMARY KEY, btree (chunk_id, chunk_seq)
 
 -- 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
+ btree2   | Index
+ gin      | Index
+ gist     | Index
+ hash     | Index
+ heap     | Table
+ heap2    | Table
+ ordering | Interface
+ spgist   | Index
+(10 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
+ btree2   | Index
+ gin      | Index
+ gist     | Index
+ hash     | Index
+ heap     | Table
+ heap2    | Table
+ ordering | Interface
+ spgist   | Index
+(10 rows)
 
 \dA h*
 List of access methods
  Name  | Type  
 -------+-------
  hash  | Index
  heap  | Table
  heap2 | Table
 (3 rows)
 
@@ -4940,46 +4944,50 @@ List of access methods
 (0 rows)
 
 \dA foo bar
 List of access methods
  Name | Type 
 ------+------
 (0 rows)
 
 \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
+ btree2   | Index     | bthandler             | 
+ 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  | 
+ ordering | Interface | ordering_ifam_handler | ordering interface
+ spgist   | Index     | spghandler            | SP-GiST index access method
+(10 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
+ btree2   | Index     | bthandler             | 
+ 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  | 
+ ordering | Interface | ordering_ifam_handler | ordering interface
+ spgist   | Index     | spghandler            | SP-GiST index access method
+(10 rows)
 
 \dA+ h*
                      List of access methods
  Name  | Type  |       Handler        |       Description        
 -------+-------+----------------------+--------------------------
  hash  | Index | hashhandler          | hash index access method
  heap  | Table | heap_tableam_handler | heap table access method
  heap2 | Table | heap_tableam_handler | 
 (3 rows)
 
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index d9ce961be2..f5b3cf98b1 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -96,20 +96,21 @@ nummultirange_test|t
 numrange_test|t
 onek|t
 onek2|t
 path_tbl|f
 person|f
 persons|f
 persons2|t
 persons3|t
 pg_aggregate|t
 pg_am|t
+pg_amimplements|t
 pg_amop|t
 pg_amproc|t
 pg_attrdef|t
 pg_attribute|t
 pg_auth_members|t
 pg_authid|t
 pg_cast|t
 pg_class|t
 pg_collation|t
 pg_constraint|t
diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql
index 9a359466ce..c102449641 100644
--- a/src/test/regress/sql/create_am.sql
+++ b/src/test/regress/sql/create_am.sql
@@ -1,17 +1,24 @@
 --
 -- Create access method tests
 --
 
--- Make gist2 over gisthandler. In fact, it would be a synonym to gist.
+-- Add synonyms to btree and gist
+CREATE ACCESS METHOD btree2 TYPE INDEX HANDLER bthandler IMPLEMENTS (ordering);
 CREATE ACCESS METHOD gist2 TYPE INDEX HANDLER gisthandler;
 
+-- Verify IMPLEMENTS
+CREATE ACCESS METHOD bogus TYPE INTERFACE HANDLER heap_tableam_handler IMPLEMENTS (ordering);
+CREATE ACCESS METHOD bogus TYPE TABLE HANDLER heap_tableam_handler IMPLEMENTS (ordering);
+CREATE ACCESS METHOD bogus TYPE INDEX HANDLER bthandler IMPLEMENTS (ordering, ordering);
+CREATE ACCESS METHOD bogus TYPE INDEX HANDLER bthandler IMPLEMENTS (heap);
+
 -- Verify return type checks for handlers
 CREATE ACCESS METHOD bogus TYPE INDEX HANDLER int4in;
 CREATE ACCESS METHOD bogus TYPE INDEX HANDLER heap_tableam_handler;
 
 
 -- Try to create gist2 index on fast_emp4000: fail because opclass doesn't exist
 CREATE INDEX grect2ind2 ON fast_emp4000 USING gist2 (home_base);
 
 -- Make operator class for boxes using gist2
 CREATE OPERATOR CLASS box_ops DEFAULT
-- 
2.30.1 (Apple Git-130)

#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Emre Hasegeli (#1)
Re: Decouple operator classes from index access methods

Emre Hasegeli <emre@hasegeli.com> writes:

I think we can benefit from higher level operator classes which can
support multiple index implementations. This is achievable by
introducing another type of access method.

I do not really understand what the point of that is?

To the extent that operator classes have any meaning at all apart
from the associated index AMs, ISTM it's that they embody specific
well-known semantics, as btree and hash opclasses do for sorting
and hashing respectively. I'm not clear on what a multi-AM
opclass notion would do for us.

I suggest the initial version to come with 2 new access methods in the
new type: hashing and ordering. We can use those in the functions
that are currently searching for the hash and btree operator classes.

Again, exactly what does that buy us, other than more complication
and overhead?

I can see some value perhaps in letting other opclasses refer to
btree and hash opclasses rather than duplicating their entries.
But that doesn't seem to be what you're proposing here.

regards, tom lane

#3Emre Hasegeli
emre@hasegeli.com
In reply to: Tom Lane (#2)
Re: Decouple operator classes from index access methods

I can see some value perhaps in letting other opclasses refer to
btree and hash opclasses rather than duplicating their entries.
But that doesn't seem to be what you're proposing here.

That's what I am proposing. My idea is to change the current btree
and hash opclasses to be under the new proposed access methods so
btree, hash, and any other index access method can refer to them.

#4Alexander Korotkov
aekorotkov@gmail.com
In reply to: Tom Lane (#2)
Re: Decouple operator classes from index access methods

On Tue, Jun 22, 2021 at 8:06 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

I suggest the initial version to come with 2 new access methods in the
new type: hashing and ordering. We can use those in the functions
that are currently searching for the hash and btree operator classes.

Again, exactly what does that buy us, other than more complication
and overhead?

I can see some value perhaps in letting other opclasses refer to
btree and hash opclasses rather than duplicating their entries.
But that doesn't seem to be what you're proposing here.

In future we could have, for instance, LSM or in-memory B-tree or
other index AM, which could use existing B-tree or hash opclasses.

But even now, we could use this decoupling to get rid of ugly
btree_gist and btree_gin. And also solve the extensibility problem
here. If an extension provides datatype with B-tree opclass, we
currently can't directly use it with GiST and GIN. So, in order to
provide B-tree-like indexing for GiST and GIN, an extension needs to
explicitly define GiST and GIN B-tree-like opclasses.

From my point of view, we can consider a decoupling patch if it will
come with an ability to use B-tree opclasses directly in GiST and GIN.

------
Regards,
Alexander Korotkov

#5Emre Hasegeli
emre@hasegeli.com
In reply to: Alexander Korotkov (#4)
Re: Decouple operator classes from index access methods

In future we could have, for instance, LSM or in-memory B-tree or
other index AM, which could use existing B-tree or hash opclasses.

This would be easily possible with my patch:

CREATE ACCESS METHOD inmemorybtree
TYPE INDEX HANDLER imbthandler
IMPLEMENTS (ordering);

But even now, we could use this decoupling to get rid of ugly
btree_gist and btree_gin. And also solve the extensibility problem
here. If an extension provides datatype with B-tree opclass, we
currently can't directly use it with GiST and GIN. So, in order to
provide B-tree-like indexing for GiST and GIN, an extension needs to
explicitly define GiST and GIN B-tree-like opclasses.

This would also be possible if we move btree_gist and btree_gin
support functions inside gist and gin access methods. The access
method support functions get the operator family. They can find which
access method this operator family belongs to, and call the
appropriate functions if it is "ordering".

#6Alexander Korotkov
aekorotkov@gmail.com
In reply to: Emre Hasegeli (#5)
Re: Decouple operator classes from index access methods

On Fri, Jun 25, 2021 at 12:18 PM Emre Hasegeli <emre@hasegeli.com> wrote:

In future we could have, for instance, LSM or in-memory B-tree or
other index AM, which could use existing B-tree or hash opclasses.

This would be easily possible with my patch:

CREATE ACCESS METHOD inmemorybtree
TYPE INDEX HANDLER imbthandler
IMPLEMENTS (ordering);

But even now, we could use this decoupling to get rid of ugly
btree_gist and btree_gin. And also solve the extensibility problem
here. If an extension provides datatype with B-tree opclass, we
currently can't directly use it with GiST and GIN. So, in order to
provide B-tree-like indexing for GiST and GIN, an extension needs to
explicitly define GiST and GIN B-tree-like opclasses.

This would also be possible if we move btree_gist and btree_gin
support functions inside gist and gin access methods. The access
method support functions get the operator family. They can find which
access method this operator family belongs to, and call the
appropriate functions if it is "ordering".

Yes, that's it. That's quite an amount of work, but I think this
would be a great illustration of the advantages of this decoupling.

------
Regards,
Alexander Korotkov