Add SQL-accessible functions for inspecting index AM properties.

Per discussion, we should provide such functions to replace the lost
ability to discover AM properties by inspecting pg_am (cf commit
65c5fcd35).  The added functionality is also meant to displace any code
that was looking directly at pg_index.indoption, since we'd rather not
believe that the bit meanings in that field are part of any client API
contract.

As future-proofing, define the SQL API to not assume that properties that
are currently AM-wide or index-wide will remain so unless they logically
must be; instead, expose them only when inquiring about a specific index
or even specific index column.  Also provide the ability for an index
AM to override the behavior.

In passing, document pg_am.amtype, overlooked in commit 473b93287.

Andrew Gierth, with kibitzing by me and others

Discussion: <87mvl5on7n.fsf@news-spur.riddles.org.uk>
This commit is contained in:
Tom Lane 2016-08-13 18:31:14 -04:00
parent 4997878193
commit ed0097e4f9
28 changed files with 1146 additions and 18 deletions

View File

@ -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;

View File

@ -529,10 +529,11 @@
</indexterm>
<para>
The catalog <structname>pg_am</structname> 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 <xref linkend="indexam">.
The catalog <structname>pg_am</structname> 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 <xref linkend="indexam">.
</para>
<table>
@ -573,10 +574,30 @@
</entry>
</row>
<row>
<entry><structfield>amtype</structfield></entry>
<entry><type>char</type></entry>
<entry></entry>
<entry>
Currently always <literal>i</literal> to indicate an index access
method; other values may be allowed in future
</entry>
</row>
</tbody>
</tgroup>
</table>
<note>
<para>
Before <productname>PostgreSQL</> 9.6, <structname>pg_am</structname>
contained many additional columns representing properties of index access
methods. That data is now only directly visible at the C code level.
However, <function>pg_index_column_has_property()</function> and related
functions have been added to allow SQL queries to inspect index access
method properties; see <xref linkend="functions-info-catalog-table">.
</para>
</note>
</sect1>

View File

@ -16289,6 +16289,18 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
<primary>pg_get_viewdef</primary>
</indexterm>
<indexterm>
<primary>pg_index_column_has_property</primary>
</indexterm>
<indexterm>
<primary>pg_index_has_property</primary>
</indexterm>
<indexterm>
<primary>pg_indexam_has_property</primary>
</indexterm>
<indexterm>
<primary>pg_options_to_table</primary>
</indexterm>
@ -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</entry>
</row>
<row>
<entry><literal><function>pg_index_column_has_property(<parameter>index_oid</parameter>, <parameter>column_no</>, <parameter>prop_name</>)</function></literal></entry>
<entry><type>boolean</type></entry>
<entry>test whether an index column has a specified property</entry>
</row>
<row>
<entry><literal><function>pg_index_has_property(<parameter>index_oid</parameter>, <parameter>prop_name</>)</function></literal></entry>
<entry><type>boolean</type></entry>
<entry>test whether an index has a specified property</entry>
</row>
<row>
<entry><literal><function>pg_indexam_has_property(<parameter>am_oid</parameter>, <parameter>prop_name</>)</function></literal></entry>
<entry><type>boolean</type></entry>
<entry>test whether an index access method has a specified property</entry>
</row>
<row>
<entry><literal><function>pg_options_to_table(<parameter>reloptions</parameter>)</function></literal></entry>
<entry><type>setof record</type></entry>
@ -16619,6 +16646,144 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
its OID.
</para>
<para>
<function>pg_index_column_has_property</function>,
<function>pg_index_has_property</function>, and
<function>pg_indexam_has_property</function> return whether the
specified index column, index, or index access method possesses the named
property. <literal>NULL</literal> 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
<xref linkend="functions-info-index-column-props"> for column properties,
<xref linkend="functions-info-index-props"> for index properties, and
<xref linkend="functions-info-indexam-props"> for access method properties.
(Note that extension access methods can define additional property names
for their indexes.)
</para>
<table id="functions-info-index-column-props">
<title>Index Column Properties</title>
<tgroup cols="2">
<thead>
<row><entry>Name</entry><entry>Description</entry></row>
</thead>
<tbody>
<row>
<entry><literal>asc</literal></entry>
<entry>Does the column sort in ascending order on a forward scan?
</entry>
</row>
<row>
<entry><literal>desc</literal></entry>
<entry>Does the column sort in descending order on a forward scan?
</entry>
</row>
<row>
<entry><literal>nulls_first</literal></entry>
<entry>Does the column sort with nulls first on a forward scan?
</entry>
</row>
<row>
<entry><literal>nulls_last</literal></entry>
<entry>Does the column sort with nulls last on a forward scan?
</entry>
</row>
<row>
<entry><literal>orderable</literal></entry>
<entry>Does the column possess any defined sort ordering?
</entry>
</row>
<row>
<entry><literal>distance_orderable</literal></entry>
<entry>Can the column be scanned in order by a <quote>distance</>
operator, for example <literal>ORDER BY col &lt;-&gt; constant</> ?
</entry>
</row>
<row>
<entry><literal>returnable</literal></entry>
<entry>Can the column value be returned by an index-only scan?
</entry>
</row>
<row>
<entry><literal>search_array</literal></entry>
<entry>Does the column natively support <literal>col = ANY(array)</>
searches?
</entry>
</row>
<row>
<entry><literal>search_nulls</literal></entry>
<entry>Does the column support <literal>IS NULL</> and
<literal>IS NOT NULL</> searches?
</entry>
</row>
</tbody>
</tgroup>
</table>
<table id="functions-info-index-props">
<title>Index Properties</title>
<tgroup cols="2">
<thead>
<row><entry>Name</entry><entry>Description</entry></row>
</thead>
<tbody>
<row>
<entry><literal>clusterable</literal></entry>
<entry>Can the index be used in a <literal>CLUSTER</> command?
</entry>
</row>
<row>
<entry><literal>index_scan</literal></entry>
<entry>Does the index support plain (non-bitmap) scans?
</entry>
</row>
<row>
<entry><literal>bitmap_scan</literal></entry>
<entry>Does the index support bitmap scans?
</entry>
</row>
<row>
<entry><literal>backward_scan</literal></entry>
<entry>Can the index be scanned backwards?
</entry>
</row>
</tbody>
</tgroup>
</table>
<table id="functions-info-indexam-props">
<title>Index Access Method Properties</title>
<tgroup cols="2">
<thead>
<row><entry>Name</entry><entry>Description</entry></row>
</thead>
<tbody>
<row>
<entry><literal>can_order</literal></entry>
<entry>Does the access method support <literal>ASC</>,
<literal>DESC</> and related keywords in
<literal>CREATE INDEX</>?
</entry>
</row>
<row>
<entry><literal>can_unique</literal></entry>
<entry>Does the access method support unique indexes?
</entry>
</row>
<row>
<entry><literal>can_multi_col</literal></entry>
<entry>Does the access method support indexes with multiple columns?
</entry>
</row>
<row>
<entry><literal>can_exclude</literal></entry>
<entry>Does the access method support exclusion constraints?
</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
<function>pg_options_to_table</function> returns the set of storage
option name/value pairs

View File

@ -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,
<para>
<programlisting>
bool
amproperty (Oid index_oid, int attno,
IndexAMProperty prop, const char *propname,
bool *res, bool *isnull);
</programlisting>
The <function>amproperty</> method allows index access methods to override
the default behavior of <function>pg_index_column_has_property</function>
and related functions.
If the access method does not have any special behavior for index property
inquiries, the <structfield>amproperty</> field in
its <structname>IndexAmRoutine</> struct can be set to NULL.
Otherwise, the <function>amproperty</> method will be called with
<parameter>index_oid</> and <parameter>attno</> both zero for
<function>pg_indexam_has_property</function> calls,
or with <parameter>index_oid</> valid and <parameter>attno</> zero for
<function>pg_index_has_property</function> calls,
or with <parameter>index_oid</> valid and <parameter>attno</> greater than
zero for <function>pg_index_column_has_property</function> calls.
<parameter>prop</> is an enum value identifying the property being tested,
while <parameter>propname</> is the original property name string.
If the core code does not recognize the property name
then <parameter>prop</> is <literal>AMPROP_UNKNOWN</>.
Access methods can define custom property names by
checking <parameter>propname</> for a match (use <function>pg_strcasecmp</>
to match, for consistency with the core code); for names known to the core
code, it's better to inspect <parameter>prop</>.
If the <structfield>amproperty</> method returns <literal>true</> then
it has determined the property test result: it must set <literal>*res</>
to the boolean value to return, or set <literal>*isnull</>
to <literal>true</> to return a NULL. (Both of the referenced variables
are initialized to <literal>false</> before the call.)
If the <structfield>amproperty</> method returns <literal>false</> then
the core code will proceed with its normal logic for determining the
property test result.
</para>
<para>
Access methods that support ordering operators should
implement <literal>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 <literal>AMPROP_RETURNABLE</> testing,
if that can be done more cheaply than by opening the index and calling
<structfield>amcanreturn</>, which is the core code's default behavior.
The default behavior should be satisfactory for all other standard
properties.
</para>
<para>
<programlisting>
bool
amvalidate (Oid opclassoid);
</programlisting>
Validate the catalog entries for the specified operator class, so far as

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -16,10 +16,13 @@
#include <math.h>
#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

View File

@ -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;

View File

@ -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",

View File

@ -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;

View File

@ -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 */
}
}

View File

@ -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;

View File

@ -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);

View File

@ -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,

View File

@ -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;

View File

@ -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 \

View File

@ -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);
}

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201608031
#define CATALOG_VERSION_NO 201608131
#endif

View File

@ -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_ ));

View File

@ -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);

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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;