]> granicus.if.org Git - postgresql/commitdiff
Add SQL-accessible functions for inspecting index AM properties.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 13 Aug 2016 22:31:14 +0000 (18:31 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 13 Aug 2016 22:31:14 +0000 (18:31 -0400)
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>

28 files changed:
contrib/bloom/blutils.c
doc/src/sgml/catalogs.sgml
doc/src/sgml/func.sgml
doc/src/sgml/indexam.sgml
src/backend/access/brin/brin.c
src/backend/access/gin/ginutil.c
src/backend/access/gist/gist.c
src/backend/access/gist/gistutil.c
src/backend/access/hash/hash.c
src/backend/access/index/amapi.c
src/backend/access/nbtree/nbtree.c
src/backend/access/nbtree/nbtutils.c
src/backend/access/spgist/spgutils.c
src/backend/catalog/index.c
src/backend/commands/opclasscmds.c
src/backend/executor/execAmi.c
src/backend/utils/adt/Makefile
src/backend/utils/adt/amutils.c [new file with mode: 0644]
src/include/access/amapi.h
src/include/access/gist_private.h
src/include/access/nbtree.h
src/include/catalog/catversion.h
src/include/catalog/pg_proc.h
src/include/utils/builtins.h
src/test/regress/expected/amutils.out [new file with mode: 0644]
src/test/regress/parallel_schedule
src/test/regress/serial_schedule
src/test/regress/sql/amutils.sql [new file with mode: 0644]

index 28fda0fa56f89dc91f89a2f12f376742544b3dfb..b5007ed6bb185fb9dbc4f75b23ede004b26a19d3 100644 (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;
index ccb9b97a8cb751857852198dbf3802640d6b0c50..4e09e06aed7bdc3f55a8f6bc2bae5dbff414f129 100644 (file)
   </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>
       </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>
 
 
index 783033403a436eb70fd7016aac71cf33055bb625..426e562b03680d999313a73ea5c02280e251d009 100644 (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
index fa4842b73fa4e82e0101dcf766ec79984a779096..b59cd0363a2eef45710c5cb03501d7a57b59a442 100644 (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
index 89bad0521b6c6d68e84c117757cd958a0cfc9936..b194d33cc5c64a29b2901b0f5dd0ef4825580c68 100644 (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;
index a2450f4687eaa8aadad4c163b9bfc98799befcc6..d9146488c4450c6828fd4df1f3348f8fd024d3ed 100644 (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;
index e8034b9cd6d013d31361006a6f6012577818ea15..9a417ca2f42199015e8f9dbe77b1c5f0d9f38ea1 100644 (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;
index fac166d4c29bb270cb7b4f5bc6fe4434abc4f5b1..26d4a646947819f8b26bc08dc69c9fa645e53c61 100644 (file)
 #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
index 30c82e191c6a6c9b16f89350420a37f8d809e93c..07496f815622480c0948389736dc055c6af4e993 100644 (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;
index d347ebcba45f7d8fa71ed98a7a496a65cbdba834..28f6cde89678c5cc289e5ded3323051d5e44ce07 100644 (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",
index 1f479735c20e86fc42b2ddb1569a0ac9bd2ed0dd..4668c5ee59f0ccc4807831f05be4cdf376348148 100644 (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;
index 83c553ca279e2db0ef1590ed38409666b5e9e9e0..5d335c7f97cd5160706778ca8a2dcef023692c1b 100644 (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 */
+       }
+}
index bc679bf75a0605b9ad620835d55c5d55da43210c..d570ae5992a87592980261714c9c606d8fa1a767 100644 (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;
index 7b30e469dfec5e55a23f997efed05e244fda84b5..b0b43cf02d8d3281c716f874f9ed1181aa1ba481 100644 (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);
index 5f665cf3a201a00e773d929f6ff841e7fa68951f..f4dfdb9642b2d71e10db0b08088247155d1784fb 100644 (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,
index 4a978adea71ce9023880eb1af85506dd9bba73a9..2587ef704626e10b0b744c645cc3a40d89b2c23d 100644 (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;
 
index 2b4ebc72b4a35b4858df8b0a66f37e2f408044de..b9e217ae49c4aa6c9692807083cc4304aaf175ca 100644 (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 \
diff --git a/src/backend/utils/adt/amutils.c b/src/backend/utils/adt/amutils.c
new file mode 100644 (file)
index 0000000..ad5e456
--- /dev/null
@@ -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);
+}
index 35f1061b3a19ae4f2543b94d69ff4dae1fe21b0b..1036cca99ca93312d57488e899ad5ac0f0198887 100644 (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);
 
index 23e0fe3197862f30b1afcb1354eaf9c55f8a67c2..123158501731a355eadd5e049bf7bab23c450f7c 100644 (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);
index 19437d286384b05c738868105347e57167b96bba..c580f51f7faf2de0fd6bf5a54d9ba19514804773 100644 (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
index 25e26dbbf6d9c0e167f0878ba0c3720ade1fba32..2ca3cd911a0225e6610ce978afbe0992d03a0b52 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201608031
+#define CATALOG_VERSION_NO     201608131
 
 #endif
index 270dd213c02452de38002109a9522344a5711403..af19c1a82b6c7e42378dc354fd753fbb15cb7870 100644 (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_ ));
index 8cebc861f47a568fb7e2046fa20ebfddc6702a31..a91be981b9873bd114c16d1459b7df7be02c663e 100644 (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);
diff --git a/src/test/regress/expected/amutils.out b/src/test/regress/expected/amutils.out
new file mode 100644 (file)
index 0000000..74f7c9f
--- /dev/null
@@ -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)
+
index 4ebad04d060e1952eaa5b28e74117c90f3ee9f15..3815182fe7a8b14b3d89bd0d052ddc342aeb9939 100644 (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
index 5c7038d6e1bb3cbd2b34cc8725c8658ef8fc8bdc..8958d8cdb9d29a46f6bc21a6b0069390794c41e1 100644 (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
diff --git a/src/test/regress/sql/amutils.sql b/src/test/regress/sql/amutils.sql
new file mode 100644 (file)
index 0000000..cec1dcb
--- /dev/null
@@ -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;