diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c
index 28fda0fa56..b5007ed6bb 100644
--- a/contrib/bloom/blutils.c
+++ b/contrib/bloom/blutils.c
@@ -129,6 +129,7 @@ blhandler(PG_FUNCTION_ARGS)
amroutine->amcanreturn = NULL;
amroutine->amcostestimate = blcostestimate;
amroutine->amoptions = bloptions;
+ amroutine->amproperty = NULL;
amroutine->amvalidate = blvalidate;
amroutine->ambeginscan = blbeginscan;
amroutine->amrescan = blrescan;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ccb9b97a8c..4e09e06aed 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -529,10 +529,11 @@
- The catalog pg_am stores information about index
- access methods. There is one row for each index access method supported by
- the system. The requirements for index access methods are discussed in
- detail in .
+ The catalog pg_am stores information about
+ relation access methods. There is one row for each access method supported
+ by the system.
+ Currently, only indexes have access methods. The requirements for index
+ access methods are discussed in detail in .
@@ -573,10 +574,30 @@
+
+ amtype
+ char
+
+
+ Currently always i to indicate an index access
+ method; other values may be allowed in future
+
+
+
+
+ Before PostgreSQL> 9.6, pg_am
+ contained many additional columns representing properties of index access
+ methods. That data is now only directly visible at the C code level.
+ However, pg_index_column_has_property() and related
+ functions have been added to allow SQL queries to inspect index access
+ method properties; see .
+
+
+
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 783033403a..426e562b03 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -16289,6 +16289,18 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
pg_get_viewdef
+
+ pg_index_column_has_property
+
+
+
+ pg_index_has_property
+
+
+
+ pg_indexam_has_property
+
+
pg_options_to_table
@@ -16476,6 +16488,21 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
materialized view; lines with fields are wrapped to specified
number of columns, pretty-printing is implied
+
+ pg_index_column_has_property(index_oid, column_no>, prop_name>)
+ boolean
+ test whether an index column has a specified property
+
+
+ pg_index_has_property(index_oid, prop_name>)
+ boolean
+ test whether an index has a specified property
+
+
+ pg_indexam_has_property(am_oid, prop_name>)
+ boolean
+ test whether an index access method has a specified property
+
pg_options_to_table(reloptions)
setof record
@@ -16619,6 +16646,144 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
its OID.
+
+ pg_index_column_has_property,
+ pg_index_has_property, and
+ pg_indexam_has_property return whether the
+ specified index column, index, or index access method possesses the named
+ property. NULL is returned if the property name is not
+ known or does not apply to the particular object, or if the OID or column
+ number does not identify a valid object. Refer to
+ for column properties,
+ for index properties, and
+ for access method properties.
+ (Note that extension access methods can define additional property names
+ for their indexes.)
+
+
+
+ Index Column Properties
+
+
+ NameDescription
+
+
+
+ asc
+ Does the column sort in ascending order on a forward scan?
+
+
+
+ desc
+ Does the column sort in descending order on a forward scan?
+
+
+
+ nulls_first
+ Does the column sort with nulls first on a forward scan?
+
+
+
+ nulls_last
+ Does the column sort with nulls last on a forward scan?
+
+
+
+ orderable
+ Does the column possess any defined sort ordering?
+
+
+
+ distance_orderable
+ Can the column be scanned in order by a distance>
+ operator, for example ORDER BY col <-> constant> ?
+
+
+
+ returnable
+ Can the column value be returned by an index-only scan?
+
+
+
+ search_array
+ Does the column natively support col = ANY(array)>
+ searches?
+
+
+
+ search_nulls
+ Does the column support IS NULL> and
+ IS NOT NULL> searches?
+
+
+
+
+
+
+
+ Index Properties
+
+
+ NameDescription
+
+
+
+ clusterable
+ Can the index be used in a CLUSTER> command?
+
+
+
+ index_scan
+ Does the index support plain (non-bitmap) scans?
+
+
+
+ bitmap_scan
+ Does the index support bitmap scans?
+
+
+
+ backward_scan
+ Can the index be scanned backwards?
+
+
+
+
+
+
+
+ Index Access Method Properties
+
+
+ NameDescription
+
+
+
+ can_order
+ Does the access method support ASC>,
+ DESC> and related keywords in
+ CREATE INDEX>?
+
+
+
+ can_unique
+ Does the access method support unique indexes?
+
+
+
+ can_multi_col
+ Does the access method support indexes with multiple columns?
+
+
+
+ can_exclude
+ Does the access method support exclusion constraints?
+
+
+
+
+
+
pg_options_to_table returns the set of storage
option name/value pairs
diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml
index fa4842b73f..b59cd0363a 100644
--- a/doc/src/sgml/indexam.sgml
+++ b/doc/src/sgml/indexam.sgml
@@ -129,6 +129,7 @@ typedef struct IndexAmRoutine
amcanreturn_function amcanreturn; /* can be NULL */
amcostestimate_function amcostestimate;
amoptions_function amoptions;
+ amproperty_function amproperty; /* can be NULL */
amvalidate_function amvalidate;
ambeginscan_function ambeginscan;
amrescan_function amrescan;
@@ -407,6 +408,55 @@ amoptions (ArrayType *reloptions,
bool
+amproperty (Oid index_oid, int attno,
+ IndexAMProperty prop, const char *propname,
+ bool *res, bool *isnull);
+
+ The amproperty> method allows index access methods to override
+ the default behavior of pg_index_column_has_property
+ and related functions.
+ If the access method does not have any special behavior for index property
+ inquiries, the amproperty> field in
+ its IndexAmRoutine> struct can be set to NULL.
+ Otherwise, the amproperty> method will be called with
+ index_oid> and attno> both zero for
+ pg_indexam_has_property calls,
+ or with index_oid> valid and attno> zero for
+ pg_index_has_property calls,
+ or with index_oid> valid and attno> greater than
+ zero for pg_index_column_has_property calls.
+ prop> is an enum value identifying the property being tested,
+ while propname> is the original property name string.
+ If the core code does not recognize the property name
+ then prop> is AMPROP_UNKNOWN>.
+ Access methods can define custom property names by
+ checking propname> for a match (use pg_strcasecmp>
+ to match, for consistency with the core code); for names known to the core
+ code, it's better to inspect prop>.
+ If the amproperty> method returns true> then
+ it has determined the property test result: it must set *res>
+ to the boolean value to return, or set *isnull>
+ to true> to return a NULL. (Both of the referenced variables
+ are initialized to false> before the call.)
+ If the amproperty> method returns false> then
+ the core code will proceed with its normal logic for determining the
+ property test result.
+
+
+
+ Access methods that support ordering operators should
+ implement AMPROP_DISTANCE_ORDERABLE> property testing, as the
+ core code does not know how to do that and will return NULL. It may
+ also be advantageous to implement AMPROP_RETURNABLE> testing,
+ if that can be done more cheaply than by opening the index and calling
+ amcanreturn>, which is the core code's default behavior.
+ The default behavior should be satisfactory for all other standard
+ properties.
+
+
+
+
+bool
amvalidate (Oid opclassoid);
Validate the catalog entries for the specified operator class, so far as
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 89bad0521b..b194d33cc5 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -102,6 +102,7 @@ brinhandler(PG_FUNCTION_ARGS)
amroutine->amcanreturn = NULL;
amroutine->amcostestimate = brincostestimate;
amroutine->amoptions = brinoptions;
+ amroutine->amproperty = NULL;
amroutine->amvalidate = brinvalidate;
amroutine->ambeginscan = brinbeginscan;
amroutine->amrescan = brinrescan;
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index a2450f4687..d9146488c4 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -57,6 +57,7 @@ ginhandler(PG_FUNCTION_ARGS)
amroutine->amcanreturn = NULL;
amroutine->amcostestimate = gincostestimate;
amroutine->amoptions = ginoptions;
+ amroutine->amproperty = NULL;
amroutine->amvalidate = ginvalidate;
amroutine->ambeginscan = ginbeginscan;
amroutine->amrescan = ginrescan;
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index e8034b9cd6..9a417ca2f4 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -79,6 +79,7 @@ gisthandler(PG_FUNCTION_ARGS)
amroutine->amcanreturn = gistcanreturn;
amroutine->amcostestimate = gistcostestimate;
amroutine->amoptions = gistoptions;
+ amroutine->amproperty = gistproperty;
amroutine->amvalidate = gistvalidate;
amroutine->ambeginscan = gistbeginscan;
amroutine->amrescan = gistrescan;
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index fac166d4c2..26d4a64694 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -16,10 +16,13 @@
#include
#include "access/gist_private.h"
+#include "access/htup_details.h"
#include "access/reloptions.h"
+#include "catalog/pg_opclass.h"
#include "storage/indexfsm.h"
#include "storage/lmgr.h"
#include "utils/builtins.h"
+#include "utils/syscache.h"
/*
@@ -836,6 +839,103 @@ gistoptions(Datum reloptions, bool validate)
return (bytea *) rdopts;
}
+/*
+ * gistproperty() -- Check boolean properties of indexes.
+ *
+ * This is optional for most AMs, but is required for GiST because the core
+ * property code doesn't support AMPROP_DISTANCE_ORDERABLE. We also handle
+ * AMPROP_RETURNABLE here to save opening the rel to call gistcanreturn.
+ */
+bool
+gistproperty(Oid index_oid, int attno,
+ IndexAMProperty prop, const char *propname,
+ bool *res, bool *isnull)
+{
+ HeapTuple tuple;
+ Form_pg_index rd_index;
+ Form_pg_opclass rd_opclass;
+ Datum datum;
+ bool disnull;
+ oidvector *indclass;
+ Oid opclass,
+ opfamily,
+ opcintype;
+ int16 procno;
+
+ /* Only answer column-level inquiries */
+ if (attno == 0)
+ return false;
+
+ /*
+ * Currently, GiST distance-ordered scans require that there be a distance
+ * function in the opclass with the default types (i.e. the one loaded
+ * into the relcache entry, see initGISTstate). So we assume that if such
+ * a function exists, then there's a reason for it (rather than grubbing
+ * through all the opfamily's operators to find an ordered one).
+ *
+ * Essentially the same code can test whether we support returning the
+ * column data, since that's true if the opclass provides a fetch proc.
+ */
+
+ switch (prop)
+ {
+ case AMPROP_DISTANCE_ORDERABLE:
+ procno = GIST_DISTANCE_PROC;
+ break;
+ case AMPROP_RETURNABLE:
+ procno = GIST_FETCH_PROC;
+ break;
+ default:
+ return false;
+ }
+
+ /* First we need to know the column's opclass. */
+
+ tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));
+ if (!HeapTupleIsValid(tuple))
+ {
+ *isnull = true;
+ return true;
+ }
+ rd_index = (Form_pg_index) GETSTRUCT(tuple);
+
+ /* caller is supposed to guarantee this */
+ Assert(attno > 0 && attno <= rd_index->indnatts);
+
+ datum = SysCacheGetAttr(INDEXRELID, tuple,
+ Anum_pg_index_indclass, &disnull);
+ Assert(!disnull);
+
+ indclass = ((oidvector *) DatumGetPointer(datum));
+ opclass = indclass->values[attno - 1];
+
+ ReleaseSysCache(tuple);
+
+ /* Now look up the opclass family and input datatype. */
+
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
+ if (!HeapTupleIsValid(tuple))
+ {
+ *isnull = true;
+ return true;
+ }
+ rd_opclass = (Form_pg_opclass) GETSTRUCT(tuple);
+
+ opfamily = rd_opclass->opcfamily;
+ opcintype = rd_opclass->opcintype;
+
+ ReleaseSysCache(tuple);
+
+ /* And now we can check whether the function is provided. */
+
+ *res = SearchSysCacheExists4(AMPROCNUM,
+ ObjectIdGetDatum(opfamily),
+ ObjectIdGetDatum(opcintype),
+ ObjectIdGetDatum(opcintype),
+ Int16GetDatum(procno));
+ return true;
+}
+
/*
* Temporary and unlogged GiST indexes are not WAL-logged, but we need LSNs
* to detect concurrent page splits anyway. This function provides a fake
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 30c82e191c..07496f8156 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -75,6 +75,7 @@ hashhandler(PG_FUNCTION_ARGS)
amroutine->amcanreturn = NULL;
amroutine->amcostestimate = hashcostestimate;
amroutine->amoptions = hashoptions;
+ amroutine->amproperty = NULL;
amroutine->amvalidate = hashvalidate;
amroutine->ambeginscan = hashbeginscan;
amroutine->amrescan = hashrescan;
diff --git a/src/backend/access/index/amapi.c b/src/backend/access/index/amapi.c
index d347ebcba4..28f6cde896 100644
--- a/src/backend/access/index/amapi.c
+++ b/src/backend/access/index/amapi.c
@@ -47,9 +47,12 @@ GetIndexAmRoutine(Oid amhandler)
/*
* GetIndexAmRoutineByAmId - look up the handler of the index access method
* with the given OID, and get its IndexAmRoutine struct.
+ *
+ * If the given OID isn't a valid index access method, returns NULL if
+ * noerror is true, else throws error.
*/
IndexAmRoutine *
-GetIndexAmRoutineByAmId(Oid amoid)
+GetIndexAmRoutineByAmId(Oid amoid, bool noerror)
{
HeapTuple tuple;
Form_pg_am amform;
@@ -58,25 +61,43 @@ GetIndexAmRoutineByAmId(Oid amoid)
/* Get handler function OID for the access method */
tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid));
if (!HeapTupleIsValid(tuple))
+ {
+ if (noerror)
+ return NULL;
elog(ERROR, "cache lookup failed for access method %u",
amoid);
+ }
amform = (Form_pg_am) GETSTRUCT(tuple);
- /* Check if it's index access method */
+ /* 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")));
+ }
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))));
+ }
ReleaseSysCache(tuple);
@@ -107,7 +128,7 @@ amvalidate(PG_FUNCTION_ARGS)
ReleaseSysCache(classtup);
- amroutine = GetIndexAmRoutineByAmId(amoid);
+ amroutine = GetIndexAmRoutineByAmId(amoid, false);
if (amroutine->amvalidate == NULL)
elog(ERROR, "function amvalidate is not defined for index access method %u",
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 1f479735c2..4668c5ee59 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -108,6 +108,7 @@ bthandler(PG_FUNCTION_ARGS)
amroutine->amcanreturn = btcanreturn;
amroutine->amcostestimate = btcostestimate;
amroutine->amoptions = btoptions;
+ amroutine->amproperty = btproperty;
amroutine->amvalidate = btvalidate;
amroutine->ambeginscan = btbeginscan;
amroutine->amrescan = btrescan;
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 83c553ca27..5d335c7f97 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -2041,3 +2041,29 @@ btoptions(Datum reloptions, bool validate)
{
return default_reloptions(reloptions, validate, RELOPT_KIND_BTREE);
}
+
+/*
+ * btproperty() -- Check boolean properties of indexes.
+ *
+ * This is optional, but handling AMPROP_RETURNABLE here saves opening the rel
+ * to call btcanreturn.
+ */
+bool
+btproperty(Oid index_oid, int attno,
+ IndexAMProperty prop, const char *propname,
+ bool *res, bool *isnull)
+{
+ switch (prop)
+ {
+ case AMPROP_RETURNABLE:
+ /* answer only for columns, not AM or whole index */
+ if (attno == 0)
+ return false;
+ /* otherwise, btree can always return data */
+ *res = true;
+ return true;
+
+ default:
+ return false; /* punt to generic code */
+ }
+}
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index bc679bf75a..d570ae5992 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -58,6 +58,7 @@ spghandler(PG_FUNCTION_ARGS)
amroutine->amcanreturn = spgcanreturn;
amroutine->amcostestimate = spgcostestimate;
amroutine->amoptions = spgoptions;
+ amroutine->amproperty = NULL;
amroutine->amvalidate = spgvalidate;
amroutine->ambeginscan = spgbeginscan;
amroutine->amrescan = spgrescan;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 7b30e469df..b0b43cf02d 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -289,7 +289,7 @@ ConstructTupleDescriptor(Relation heapRelation,
int i;
/* We need access to the index AM's API struct */
- amroutine = GetIndexAmRoutineByAmId(accessMethodObjectId);
+ amroutine = GetIndexAmRoutineByAmId(accessMethodObjectId, false);
/* ... and to the table's tuple descriptor */
heapTupDesc = RelationGetDescr(heapRelation);
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index 5f665cf3a2..f4dfdb9642 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -366,7 +366,7 @@ DefineOpClass(CreateOpClassStmt *stmt)
stmt->amname)));
amoid = HeapTupleGetOid(tup);
- amroutine = GetIndexAmRoutineByAmId(amoid);
+ amroutine = GetIndexAmRoutineByAmId(amoid, false);
ReleaseSysCache(tup);
maxOpNumber = amroutine->amstrategies;
@@ -791,7 +791,7 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
stmt->amname)));
amoid = HeapTupleGetOid(tup);
- amroutine = GetIndexAmRoutineByAmId(amoid);
+ amroutine = GetIndexAmRoutineByAmId(amoid, false);
ReleaseSysCache(tup);
maxOpNumber = amroutine->amstrategies;
@@ -1103,7 +1103,7 @@ assignOperTypes(OpFamilyMember *member, Oid amoid, Oid typeoid)
* the family has been created but not yet populated with the required
* operators.)
*/
- IndexAmRoutine *amroutine = GetIndexAmRoutineByAmId(amoid);
+ IndexAmRoutine *amroutine = GetIndexAmRoutineByAmId(amoid, false);
if (!amroutine->amcanorderbyop)
ereport(ERROR,
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 4a978adea7..2587ef7046 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -555,7 +555,7 @@ IndexSupportsBackwardScan(Oid indexid)
idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
/* Fetch the index AM's API struct */
- amroutine = GetIndexAmRoutineByAmId(idxrelrec->relam);
+ amroutine = GetIndexAmRoutineByAmId(idxrelrec->relam, false);
result = amroutine->amcanbackward;
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 2b4ebc72b4..b9e217ae49 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -9,7 +9,7 @@ top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
# keep this list arranged alphabetically or it gets to be a mess
-OBJS = acl.o arrayfuncs.o array_expanded.o array_selfuncs.o \
+OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
array_typanalyze.o array_userfuncs.o arrayutils.o ascii.o \
bool.o cash.o char.o date.o datetime.o datum.o dbsize.o domains.o \
encode.o enum.o expandeddatum.o \
diff --git a/src/backend/utils/adt/amutils.c b/src/backend/utils/adt/amutils.c
new file mode 100644
index 0000000000..ad5e45674b
--- /dev/null
+++ b/src/backend/utils/adt/amutils.c
@@ -0,0 +1,390 @@
+/*-------------------------------------------------------------------------
+ *
+ * amutils.c
+ * SQL-level APIs related to index access methods.
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/amutils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/htup_details.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_index.h"
+#include "utils/builtins.h"
+#include "utils/syscache.h"
+
+
+/* Convert string property name to enum, for efficiency */
+struct am_propname
+{
+ const char *name;
+ IndexAMProperty prop;
+};
+
+static const struct am_propname am_propnames[] =
+{
+ {
+ "asc", AMPROP_ASC
+ },
+ {
+ "desc", AMPROP_DESC
+ },
+ {
+ "nulls_first", AMPROP_NULLS_FIRST
+ },
+ {
+ "nulls_last", AMPROP_NULLS_LAST
+ },
+ {
+ "orderable", AMPROP_ORDERABLE
+ },
+ {
+ "distance_orderable", AMPROP_DISTANCE_ORDERABLE
+ },
+ {
+ "returnable", AMPROP_RETURNABLE
+ },
+ {
+ "search_array", AMPROP_SEARCH_ARRAY
+ },
+ {
+ "search_nulls", AMPROP_SEARCH_NULLS
+ },
+ {
+ "clusterable", AMPROP_CLUSTERABLE
+ },
+ {
+ "index_scan", AMPROP_INDEX_SCAN
+ },
+ {
+ "bitmap_scan", AMPROP_BITMAP_SCAN
+ },
+ {
+ "backward_scan", AMPROP_BACKWARD_SCAN
+ },
+ {
+ "can_order", AMPROP_CAN_ORDER
+ },
+ {
+ "can_unique", AMPROP_CAN_UNIQUE
+ },
+ {
+ "can_multi_col", AMPROP_CAN_MULTI_COL
+ },
+ {
+ "can_exclude", AMPROP_CAN_EXCLUDE
+ }
+};
+
+static IndexAMProperty
+lookup_prop_name(const char *name)
+{
+ int i;
+
+ for (i = 0; i < lengthof(am_propnames); i++)
+ {
+ if (pg_strcasecmp(am_propnames[i].name, name) == 0)
+ return am_propnames[i].prop;
+ }
+
+ /* We do not throw an error, so that AMs can define their own properties */
+ return AMPROP_UNKNOWN;
+}
+
+/*
+ * Common code for properties that are just bit tests of indoptions.
+ *
+ * relid/attno: identify the index column to test the indoptions of.
+ * guard: if false, a boolean false result is forced (saves code in caller).
+ * iopt_mask: mask for interesting indoption bit.
+ * iopt_expect: value for a "true" result (should be 0 or iopt_mask).
+ *
+ * Returns false to indicate a NULL result (for "unknown/inapplicable"),
+ * otherwise sets *res to the boolean value to return.
+ */
+static bool
+test_indoption(Oid relid, int attno, bool guard,
+ int16 iopt_mask, int16 iopt_expect,
+ bool *res)
+{
+ HeapTuple tuple;
+ Form_pg_index rd_index;
+ Datum datum;
+ bool isnull;
+ int2vector *indoption;
+ int16 indoption_val;
+
+ if (!guard)
+ {
+ *res = false;
+ return true;
+ }
+
+ tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ return false;
+ rd_index = (Form_pg_index) GETSTRUCT(tuple);
+
+ Assert(relid == rd_index->indexrelid);
+ Assert(attno > 0 && attno <= rd_index->indnatts);
+
+ datum = SysCacheGetAttr(INDEXRELID, tuple,
+ Anum_pg_index_indoption, &isnull);
+ Assert(!isnull);
+
+ indoption = ((int2vector *) DatumGetPointer(datum));
+ indoption_val = indoption->values[attno - 1];
+
+ *res = (indoption_val & iopt_mask) == iopt_expect;
+
+ ReleaseSysCache(tuple);
+
+ return true;
+}
+
+
+/*
+ * Test property of an index AM, index, or index column.
+ *
+ * This is common code for different SQL-level funcs, so the amoid and
+ * index_oid parameters are mutually exclusive; we look up the amoid from the
+ * index_oid if needed, or if no index oid is given, we're looking at AM-wide
+ * properties.
+ */
+static Datum
+indexam_property(FunctionCallInfo fcinfo,
+ const char *propname,
+ Oid amoid, Oid index_oid, int attno)
+{
+ bool res = false;
+ bool isnull = false;
+ int natts = 0;
+ IndexAMProperty prop;
+ IndexAmRoutine *routine;
+
+ /* Try to convert property name to enum (no error if not known) */
+ prop = lookup_prop_name(propname);
+
+ /* If we have an index OID, look up the AM, and get # of columns too */
+ if (OidIsValid(index_oid))
+ {
+ HeapTuple tuple;
+ Form_pg_class rd_rel;
+
+ Assert(!OidIsValid(amoid));
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(index_oid));
+ if (!HeapTupleIsValid(tuple))
+ PG_RETURN_NULL();
+ rd_rel = (Form_pg_class) GETSTRUCT(tuple);
+ if (rd_rel->relkind != RELKIND_INDEX)
+ {
+ ReleaseSysCache(tuple);
+ PG_RETURN_NULL();
+ }
+ amoid = rd_rel->relam;
+ natts = rd_rel->relnatts;
+ ReleaseSysCache(tuple);
+ }
+
+ /*
+ * At this point, either index_oid == InvalidOid or it's a valid index
+ * OID. Also, after this test, either attno == 0 for index-wide or
+ * AM-wide tests, or it's a valid column number in a valid index.
+ */
+ if (attno < 0 || attno > natts)
+ PG_RETURN_NULL();
+
+ /*
+ * Get AM information. If we don't have a valid AM OID, return NULL.
+ */
+ routine = GetIndexAmRoutineByAmId(amoid, true);
+ if (routine == NULL)
+ PG_RETURN_NULL();
+
+ /*
+ * If there's an AM property routine, give it a chance to override the
+ * generic logic. Proceed if it returns false.
+ */
+ if (routine->amproperty &&
+ routine->amproperty(index_oid, attno, prop, propname,
+ &res, &isnull))
+ {
+ if (isnull)
+ PG_RETURN_NULL();
+ PG_RETURN_BOOL(res);
+ }
+
+ if (attno > 0)
+ {
+ /* Handle column-level properties */
+ switch (prop)
+ {
+ case AMPROP_ASC:
+ if (test_indoption(index_oid, attno, routine->amcanorder,
+ INDOPTION_DESC, 0, &res))
+ PG_RETURN_BOOL(res);
+ PG_RETURN_NULL();
+
+ case AMPROP_DESC:
+ if (test_indoption(index_oid, attno, routine->amcanorder,
+ INDOPTION_DESC, INDOPTION_DESC, &res))
+ PG_RETURN_BOOL(res);
+ PG_RETURN_NULL();
+
+ case AMPROP_NULLS_FIRST:
+ if (test_indoption(index_oid, attno, routine->amcanorder,
+ INDOPTION_NULLS_FIRST, INDOPTION_NULLS_FIRST, &res))
+ PG_RETURN_BOOL(res);
+ PG_RETURN_NULL();
+
+ case AMPROP_NULLS_LAST:
+ if (test_indoption(index_oid, attno, routine->amcanorder,
+ INDOPTION_NULLS_FIRST, 0, &res))
+ PG_RETURN_BOOL(res);
+ PG_RETURN_NULL();
+
+ case AMPROP_ORDERABLE:
+ PG_RETURN_BOOL(routine->amcanorder);
+
+ case AMPROP_DISTANCE_ORDERABLE:
+
+ /*
+ * The conditions for whether a column is distance-orderable
+ * are really up to the AM (at time of writing, only GiST
+ * supports it at all). The planner has its own idea based on
+ * whether it finds an operator with amoppurpose 'o', but
+ * getting there from just the index column type seems like a
+ * lot of work. So instead we expect the AM to handle this in
+ * its amproperty routine. The generic result is to return
+ * false if the AM says it never supports this, and null
+ * otherwise (meaning we don't know).
+ */
+ if (!routine->amcanorderbyop)
+ PG_RETURN_BOOL(false);
+ PG_RETURN_NULL();
+
+ case AMPROP_RETURNABLE:
+ if (!routine->amcanreturn)
+ PG_RETURN_BOOL(false);
+
+ /*
+ * If possible, the AM should handle this test in its
+ * amproperty function without opening the rel. But this is
+ * the generic fallback if it does not.
+ */
+ {
+ Relation indexrel = index_open(index_oid, AccessShareLock);
+
+ res = index_can_return(indexrel, attno);
+ index_close(indexrel, AccessShareLock);
+ }
+
+ PG_RETURN_BOOL(res);
+
+ case AMPROP_SEARCH_ARRAY:
+ PG_RETURN_BOOL(routine->amsearcharray);
+
+ case AMPROP_SEARCH_NULLS:
+ PG_RETURN_BOOL(routine->amsearchnulls);
+
+ default:
+ PG_RETURN_NULL();
+ }
+ }
+
+ if (OidIsValid(index_oid))
+ {
+ /*
+ * Handle index-level properties. Currently, these only depend on the
+ * AM, but that might not be true forever, so we make users name an
+ * index not just an AM.
+ */
+ switch (prop)
+ {
+ case AMPROP_CLUSTERABLE:
+ PG_RETURN_BOOL(routine->amclusterable);
+
+ case AMPROP_INDEX_SCAN:
+ PG_RETURN_BOOL(routine->amgettuple ? true : false);
+
+ case AMPROP_BITMAP_SCAN:
+ PG_RETURN_BOOL(routine->amgetbitmap ? true : false);
+
+ case AMPROP_BACKWARD_SCAN:
+ PG_RETURN_BOOL(routine->amcanbackward);
+
+ default:
+ PG_RETURN_NULL();
+ }
+ }
+
+ /*
+ * Handle AM-level properties (those that control what you can say in
+ * CREATE INDEX).
+ */
+ switch (prop)
+ {
+ case AMPROP_CAN_ORDER:
+ PG_RETURN_BOOL(routine->amcanorder);
+
+ case AMPROP_CAN_UNIQUE:
+ PG_RETURN_BOOL(routine->amcanunique);
+
+ case AMPROP_CAN_MULTI_COL:
+ PG_RETURN_BOOL(routine->amcanmulticol);
+
+ case AMPROP_CAN_EXCLUDE:
+ PG_RETURN_BOOL(routine->amgettuple ? true : false);
+
+ default:
+ PG_RETURN_NULL();
+ }
+}
+
+/*
+ * Test property of an AM specified by AM OID
+ */
+Datum
+pg_indexam_has_property(PG_FUNCTION_ARGS)
+{
+ Oid amoid = PG_GETARG_OID(0);
+ char *propname = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+ return indexam_property(fcinfo, propname, amoid, InvalidOid, 0);
+}
+
+/*
+ * Test property of an index specified by index OID
+ */
+Datum
+pg_index_has_property(PG_FUNCTION_ARGS)
+{
+ Oid relid = PG_GETARG_OID(0);
+ char *propname = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+ return indexam_property(fcinfo, propname, InvalidOid, relid, 0);
+}
+
+/*
+ * Test property of an index column specified by index OID and column number
+ */
+Datum
+pg_index_column_has_property(PG_FUNCTION_ARGS)
+{
+ Oid relid = PG_GETARG_OID(0);
+ int32 attno = PG_GETARG_INT32(1);
+ char *propname = text_to_cstring(PG_GETARG_TEXT_PP(2));
+
+ /* Reject attno 0 immediately, so that attno > 0 identifies this case */
+ if (attno <= 0)
+ PG_RETURN_NULL();
+
+ return indexam_property(fcinfo, propname, InvalidOid, relid, attno);
+}
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 35f1061b3a..1036cca99c 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -26,6 +26,34 @@ struct IndexPath;
struct IndexInfo;
+/*
+ * Properties for amproperty API. This list covers properties known to the
+ * core code, but an index AM can define its own properties, by matching the
+ * string property name.
+ */
+typedef enum IndexAMProperty
+{
+ AMPROP_UNKNOWN = 0, /* anything not known to core code */
+ AMPROP_ASC, /* column properties */
+ AMPROP_DESC,
+ AMPROP_NULLS_FIRST,
+ AMPROP_NULLS_LAST,
+ AMPROP_ORDERABLE,
+ AMPROP_DISTANCE_ORDERABLE,
+ AMPROP_RETURNABLE,
+ AMPROP_SEARCH_ARRAY,
+ AMPROP_SEARCH_NULLS,
+ AMPROP_CLUSTERABLE, /* index properties */
+ AMPROP_INDEX_SCAN,
+ AMPROP_BITMAP_SCAN,
+ AMPROP_BACKWARD_SCAN,
+ AMPROP_CAN_ORDER, /* AM properties */
+ AMPROP_CAN_UNIQUE,
+ AMPROP_CAN_MULTI_COL,
+ AMPROP_CAN_EXCLUDE
+} IndexAMProperty;
+
+
/*
* Callback function signatures --- see indexam.sgml for more info.
*/
@@ -72,6 +100,11 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root,
typedef bytea *(*amoptions_function) (Datum reloptions,
bool validate);
+/* report AM, index, or index column property */
+typedef bool (*amproperty_function) (Oid index_oid, int attno,
+ IndexAMProperty prop, const char *propname,
+ bool *res, bool *isnull);
+
/* validate definition of an opclass for this AM */
typedef bool (*amvalidate_function) (Oid opclassoid);
@@ -154,6 +187,7 @@ typedef struct IndexAmRoutine
amcanreturn_function amcanreturn; /* can be NULL */
amcostestimate_function amcostestimate;
amoptions_function amoptions;
+ amproperty_function amproperty; /* can be NULL */
amvalidate_function amvalidate;
ambeginscan_function ambeginscan;
amrescan_function amrescan;
@@ -167,7 +201,7 @@ typedef struct IndexAmRoutine
/* Functions in access/index/amapi.c */
extern IndexAmRoutine *GetIndexAmRoutine(Oid amhandler);
-extern IndexAmRoutine *GetIndexAmRoutineByAmId(Oid amoid);
+extern IndexAmRoutine *GetIndexAmRoutineByAmId(Oid amoid, bool noerror);
extern Datum amvalidate(PG_FUNCTION_ARGS);
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 23e0fe3197..1231585017 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -492,6 +492,9 @@ extern bool gistvalidate(Oid opclassoid);
#define GIST_DEFAULT_FILLFACTOR 90
extern bytea *gistoptions(Datum reloptions, bool validate);
+extern bool gistproperty(Oid index_oid, int attno,
+ IndexAMProperty prop, const char *propname,
+ bool *res, bool *isnull);
extern bool gistfitpage(IndexTuple *itvec, int len);
extern bool gistnospace(Page page, IndexTuple *itvec, int len, OffsetNumber todelete, Size freespace);
extern void gistcheckpage(Relation rel, Buffer buf);
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 19437d2863..c580f51f7f 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -748,6 +748,9 @@ extern void _bt_end_vacuum_callback(int code, Datum arg);
extern Size BTreeShmemSize(void);
extern void BTreeShmemInit(void);
extern bytea *btoptions(Datum reloptions, bool validate);
+extern bool btproperty(Oid index_oid, int attno,
+ IndexAMProperty prop, const char *propname,
+ bool *res, bool *isnull);
/*
* prototypes for functions in nbtvalidate.c
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 25e26dbbf6..2ca3cd911a 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201608031
+#define CATALOG_VERSION_NO 201608131
#endif
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 270dd213c0..af19c1a82b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -563,11 +563,18 @@ DATA(insert OID = 334 ( spghandler PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0
DESCR("spgist index access method handler");
DATA(insert OID = 335 ( brinhandler PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 325 "2281" _null_ _null_ _null_ _null_ _null_ brinhandler _null_ _null_ _null_ ));
DESCR("brin index access method handler");
+DATA(insert OID = 3952 ( brin_summarize_new_values PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 23 "2205" _null_ _null_ _null_ _null_ _null_ brin_summarize_new_values _null_ _null_ _null_ ));
+DESCR("brin: standalone scan new table pages");
DATA(insert OID = 338 ( amvalidate PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 16 "26" _null_ _null_ _null_ _null_ _null_ amvalidate _null_ _null_ _null_ ));
DESCR("validate an operator class");
-DATA(insert OID = 3952 ( brin_summarize_new_values PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 23 "2205" _null_ _null_ _null_ _null_ _null_ brin_summarize_new_values _null_ _null_ _null_ ));
-DESCR("brin: standalone scan new table pages");
+
+DATA(insert OID = 636 ( pg_indexam_has_property PGNSP PGUID 12 1 0 0 0 f f f f t f s s 2 0 16 "26 25" _null_ _null_ _null_ _null_ _null_ pg_indexam_has_property _null_ _null_ _null_ ));
+DESCR("test property of an index access method");
+DATA(insert OID = 637 ( pg_index_has_property PGNSP PGUID 12 1 0 0 0 f f f f t f s s 2 0 16 "2205 25" _null_ _null_ _null_ _null_ _null_ pg_index_has_property _null_ _null_ _null_ ));
+DESCR("test property of an index");
+DATA(insert OID = 638 ( pg_index_column_has_property PGNSP PGUID 12 1 0 0 0 f f f f t f s s 3 0 16 "2205 23 25" _null_ _null_ _null_ _null_ _null_ pg_index_column_has_property _null_ _null_ _null_ ));
+DESCR("test property of an index column");
DATA(insert OID = 339 ( poly_same PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "604 604" _null_ _null_ _null_ _null_ _null_ poly_same _null_ _null_ _null_ ));
DATA(insert OID = 340 ( poly_contain PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "604 604" _null_ _null_ _null_ _null_ _null_ poly_contain _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc861f4..a91be981b9 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -108,6 +108,11 @@ extern Datum pg_has_role_id_id(PG_FUNCTION_ARGS);
extern Datum pg_has_role_name(PG_FUNCTION_ARGS);
extern Datum pg_has_role_id(PG_FUNCTION_ARGS);
+/* amutils.c */
+extern Datum pg_indexam_has_property(PG_FUNCTION_ARGS);
+extern Datum pg_index_has_property(PG_FUNCTION_ARGS);
+extern Datum pg_index_column_has_property(PG_FUNCTION_ARGS);
+
/* bool.c */
extern Datum boolin(PG_FUNCTION_ARGS);
extern Datum boolout(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/amutils.out b/src/test/regress/expected/amutils.out
new file mode 100644
index 0000000000..74f7c9f1fd
--- /dev/null
+++ b/src/test/regress/expected/amutils.out
@@ -0,0 +1,208 @@
+--
+-- Test index AM property-reporting functions
+--
+select prop,
+ pg_indexam_has_property(a.oid, prop) as "AM",
+ pg_index_has_property('onek_hundred'::regclass, prop) as "Index",
+ pg_index_column_has_property('onek_hundred'::regclass, 1, prop) as "Column"
+ from pg_am a,
+ unnest(array['asc', 'desc', 'nulls_first', 'nulls_last',
+ 'orderable', 'distance_orderable', 'returnable',
+ 'search_array', 'search_nulls',
+ 'clusterable', 'index_scan', 'bitmap_scan',
+ 'backward_scan',
+ 'can_order', 'can_unique', 'can_multi_col',
+ 'can_exclude',
+ 'bogus']::text[])
+ with ordinality as u(prop,ord)
+ where a.amname = 'btree'
+ order by ord;
+ prop | AM | Index | Column
+--------------------+----+-------+--------
+ asc | | | t
+ desc | | | f
+ nulls_first | | | f
+ nulls_last | | | t
+ orderable | | | t
+ distance_orderable | | | f
+ returnable | | | t
+ search_array | | | t
+ search_nulls | | | t
+ clusterable | | t |
+ index_scan | | t |
+ bitmap_scan | | t |
+ backward_scan | | t |
+ can_order | t | |
+ can_unique | t | |
+ can_multi_col | t | |
+ can_exclude | t | |
+ bogus | | |
+(18 rows)
+
+select prop,
+ pg_indexam_has_property(a.oid, prop) as "AM",
+ pg_index_has_property('gcircleind'::regclass, prop) as "Index",
+ pg_index_column_has_property('gcircleind'::regclass, 1, prop) as "Column"
+ from pg_am a,
+ unnest(array['asc', 'desc', 'nulls_first', 'nulls_last',
+ 'orderable', 'distance_orderable', 'returnable',
+ 'search_array', 'search_nulls',
+ 'clusterable', 'index_scan', 'bitmap_scan',
+ 'backward_scan',
+ 'can_order', 'can_unique', 'can_multi_col',
+ 'can_exclude',
+ 'bogus']::text[])
+ with ordinality as u(prop,ord)
+ where a.amname = 'gist'
+ order by ord;
+ prop | AM | Index | Column
+--------------------+----+-------+--------
+ asc | | | f
+ desc | | | f
+ nulls_first | | | f
+ nulls_last | | | f
+ orderable | | | f
+ distance_orderable | | | t
+ returnable | | | f
+ search_array | | | f
+ search_nulls | | | t
+ clusterable | | t |
+ index_scan | | t |
+ bitmap_scan | | t |
+ backward_scan | | f |
+ can_order | f | |
+ can_unique | f | |
+ can_multi_col | t | |
+ can_exclude | t | |
+ bogus | | |
+(18 rows)
+
+select prop,
+ pg_index_column_has_property('onek_hundred'::regclass, 1, prop) as btree,
+ pg_index_column_has_property('hash_i4_index'::regclass, 1, prop) as hash,
+ pg_index_column_has_property('gcircleind'::regclass, 1, prop) as gist,
+ pg_index_column_has_property('sp_radix_ind'::regclass, 1, prop) as spgist,
+ pg_index_column_has_property('botharrayidx'::regclass, 1, prop) as gin,
+ pg_index_column_has_property('brinidx'::regclass, 1, prop) as brin
+ from unnest(array['asc', 'desc', 'nulls_first', 'nulls_last',
+ 'orderable', 'distance_orderable', 'returnable',
+ 'search_array', 'search_nulls',
+ 'bogus']::text[])
+ with ordinality as u(prop,ord)
+ order by ord;
+ prop | btree | hash | gist | spgist | gin | brin
+--------------------+-------+------+------+--------+-----+------
+ asc | t | f | f | f | f | f
+ desc | f | f | f | f | f | f
+ nulls_first | f | f | f | f | f | f
+ nulls_last | t | f | f | f | f | f
+ orderable | t | f | f | f | f | f
+ distance_orderable | f | f | t | f | f | f
+ returnable | t | f | f | t | f | f
+ search_array | t | f | f | f | f | f
+ search_nulls | t | f | t | t | f | t
+ bogus | | | | | |
+(10 rows)
+
+select prop,
+ pg_index_has_property('onek_hundred'::regclass, prop) as btree,
+ pg_index_has_property('hash_i4_index'::regclass, prop) as hash,
+ pg_index_has_property('gcircleind'::regclass, prop) as gist,
+ pg_index_has_property('sp_radix_ind'::regclass, prop) as spgist,
+ pg_index_has_property('botharrayidx'::regclass, prop) as gin,
+ pg_index_has_property('brinidx'::regclass, prop) as brin
+ from unnest(array['clusterable', 'index_scan', 'bitmap_scan',
+ 'backward_scan',
+ 'bogus']::text[])
+ with ordinality as u(prop,ord)
+ order by ord;
+ prop | btree | hash | gist | spgist | gin | brin
+---------------+-------+------+------+--------+-----+------
+ clusterable | t | f | t | f | f | f
+ index_scan | t | t | t | t | f | f
+ bitmap_scan | t | t | t | t | t | t
+ backward_scan | t | t | f | f | f | f
+ bogus | | | | | |
+(5 rows)
+
+select amname, prop, pg_indexam_has_property(a.oid, prop) as p
+ from pg_am a,
+ unnest(array['can_order', 'can_unique', 'can_multi_col',
+ 'can_exclude', 'bogus']::text[])
+ with ordinality as u(prop,ord)
+ where amtype = 'i'
+ order by amname, ord;
+ amname | prop | p
+--------+---------------+---
+ brin | can_order | f
+ brin | can_unique | f
+ brin | can_multi_col | t
+ brin | can_exclude | f
+ brin | bogus |
+ btree | can_order | t
+ btree | can_unique | t
+ btree | can_multi_col | t
+ btree | can_exclude | t
+ btree | bogus |
+ gin | can_order | f
+ gin | can_unique | f
+ gin | can_multi_col | t
+ gin | can_exclude | f
+ gin | bogus |
+ gist | can_order | f
+ gist | can_unique | f
+ gist | can_multi_col | t
+ gist | can_exclude | t
+ gist | bogus |
+ hash | can_order | f
+ hash | can_unique | f
+ hash | can_multi_col | f
+ hash | can_exclude | t
+ hash | bogus |
+ spgist | can_order | f
+ spgist | can_unique | f
+ spgist | can_multi_col | f
+ spgist | can_exclude | t
+ spgist | bogus |
+(30 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'),
+ (6, 'bogus')) v2(idx,prop),
+ generate_series(1,4) col
+ order by col, idx;
+ col | prop | pg_index_column_has_property
+-----+-------------+------------------------------
+ 1 | orderable | t
+ 1 | asc | f
+ 1 | desc | t
+ 1 | nulls_first | t
+ 1 | nulls_last | f
+ 1 | bogus |
+ 2 | orderable | t
+ 2 | asc | t
+ 2 | desc | f
+ 2 | nulls_first | f
+ 2 | nulls_last | t
+ 2 | bogus |
+ 3 | orderable | t
+ 3 | asc | t
+ 3 | desc | f
+ 3 | nulls_first | t
+ 3 | nulls_last | f
+ 3 | bogus |
+ 4 | orderable | t
+ 4 | asc | t
+ 4 | desc | f
+ 4 | nulls_first | f
+ 4 | nulls_last | t
+ 4 | bogus |
+(24 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 4ebad04d06..3815182fe7 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -92,7 +92,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
test: alter_generic alter_operator misc psql async dbsize misc_functions
# rules cannot run concurrently with any test that creates a view
-test: rules psql_crosstab select_parallel
+test: rules psql_crosstab select_parallel amutils
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 5c7038d6e1..8958d8cdb9 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -126,6 +126,7 @@ test: misc_functions
test: rules
test: psql_crosstab
test: select_parallel
+test: amutils
test: select_views
test: portals_p2
test: foreign_key
diff --git a/src/test/regress/sql/amutils.sql b/src/test/regress/sql/amutils.sql
new file mode 100644
index 0000000000..cec1dcb53b
--- /dev/null
+++ b/src/test/regress/sql/amutils.sql
@@ -0,0 +1,87 @@
+--
+-- Test index AM property-reporting functions
+--
+
+select prop,
+ pg_indexam_has_property(a.oid, prop) as "AM",
+ pg_index_has_property('onek_hundred'::regclass, prop) as "Index",
+ pg_index_column_has_property('onek_hundred'::regclass, 1, prop) as "Column"
+ from pg_am a,
+ unnest(array['asc', 'desc', 'nulls_first', 'nulls_last',
+ 'orderable', 'distance_orderable', 'returnable',
+ 'search_array', 'search_nulls',
+ 'clusterable', 'index_scan', 'bitmap_scan',
+ 'backward_scan',
+ 'can_order', 'can_unique', 'can_multi_col',
+ 'can_exclude',
+ 'bogus']::text[])
+ with ordinality as u(prop,ord)
+ where a.amname = 'btree'
+ order by ord;
+
+select prop,
+ pg_indexam_has_property(a.oid, prop) as "AM",
+ pg_index_has_property('gcircleind'::regclass, prop) as "Index",
+ pg_index_column_has_property('gcircleind'::regclass, 1, prop) as "Column"
+ from pg_am a,
+ unnest(array['asc', 'desc', 'nulls_first', 'nulls_last',
+ 'orderable', 'distance_orderable', 'returnable',
+ 'search_array', 'search_nulls',
+ 'clusterable', 'index_scan', 'bitmap_scan',
+ 'backward_scan',
+ 'can_order', 'can_unique', 'can_multi_col',
+ 'can_exclude',
+ 'bogus']::text[])
+ with ordinality as u(prop,ord)
+ where a.amname = 'gist'
+ order by ord;
+
+select prop,
+ pg_index_column_has_property('onek_hundred'::regclass, 1, prop) as btree,
+ pg_index_column_has_property('hash_i4_index'::regclass, 1, prop) as hash,
+ pg_index_column_has_property('gcircleind'::regclass, 1, prop) as gist,
+ pg_index_column_has_property('sp_radix_ind'::regclass, 1, prop) as spgist,
+ pg_index_column_has_property('botharrayidx'::regclass, 1, prop) as gin,
+ pg_index_column_has_property('brinidx'::regclass, 1, prop) as brin
+ from unnest(array['asc', 'desc', 'nulls_first', 'nulls_last',
+ 'orderable', 'distance_orderable', 'returnable',
+ 'search_array', 'search_nulls',
+ 'bogus']::text[])
+ with ordinality as u(prop,ord)
+ order by ord;
+
+select prop,
+ pg_index_has_property('onek_hundred'::regclass, prop) as btree,
+ pg_index_has_property('hash_i4_index'::regclass, prop) as hash,
+ pg_index_has_property('gcircleind'::regclass, prop) as gist,
+ pg_index_has_property('sp_radix_ind'::regclass, prop) as spgist,
+ pg_index_has_property('botharrayidx'::regclass, prop) as gin,
+ pg_index_has_property('brinidx'::regclass, prop) as brin
+ from unnest(array['clusterable', 'index_scan', 'bitmap_scan',
+ 'backward_scan',
+ 'bogus']::text[])
+ with ordinality as u(prop,ord)
+ order by ord;
+
+select amname, prop, pg_indexam_has_property(a.oid, prop) as p
+ from pg_am a,
+ unnest(array['can_order', 'can_unique', 'can_multi_col',
+ 'can_exclude', 'bogus']::text[])
+ with ordinality as u(prop,ord)
+ where amtype = 'i'
+ order by amname, ord;
+
+--
+-- 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'),
+ (6, 'bogus')) v2(idx,prop),
+ generate_series(1,4) col
+ order by col, idx;