Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Support index INCLUDE in the AM properties interface.
authorAndrew Gierth <rhodiumtoad@postgresql.org>
Sun, 8 Apr 2018 05:02:05 +0000 (06:02 +0100)
committerAndrew Gierth <rhodiumtoad@postgresql.org>
Sun, 8 Apr 2018 05:02:05 +0000 (06:02 +0100)
This rectifies an oversight in commit 8224de4f4, by adding a new
property 'can_include' for pg_indexam_has_property, and adjusting the
results of pg_index_column_has_property to give more appropriate
results for INCLUDEd columns.

doc/src/sgml/func.sgml
src/backend/utils/adt/amutils.c
src/include/access/amapi.h
src/test/regress/expected/amutils.out
src/test/regress/sql/amutils.sql

index 3dbfa1dec342293b8760f6bb62e0c9bce32a94ef..3bf21477adb3dc0d46abce0dd760ffa06e9e0a68 100644 (file)
@@ -17555,6 +17555,12 @@ SELECT currval(pg_get_serial_sequence('sometable', 'id'));
       <entry>Does the access method support exclusion constraints?
       </entry>
      </row>
+     <row>
+      <entry><literal>can_include</literal></entry>
+      <entry>Does the access method support the <literal>INCLUDE</literal>
+        clause of <literal>CREATE INDEX</literal>?
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
index 0f7ceb62eb56458645282c30bfa23f8540227c8f..0f8ad4ef0fa2469124535594e1448eeea9a83404 100644 (file)
@@ -80,7 +80,10 @@ static const struct am_propname am_propnames[] =
    },
    {
        "can_exclude", AMPROP_CAN_EXCLUDE
-   }
+   },
+   {
+       "can_include", AMPROP_CAN_INCLUDE
+   },
 };
 
 static IndexAMProperty
@@ -101,7 +104,8 @@ lookup_prop_name(const char *name)
 /*
  * Common code for properties that are just bit tests of indoptions.
  *
- * relid/attno: identify the index column to test the indoptions of.
+ * tuple: the pg_index heaptuple
+ * 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).
@@ -110,12 +114,10 @@ lookup_prop_name(const char *name)
  * otherwise sets *res to the boolean value to return.
  */
 static bool
-test_indoption(Oid relid, int attno, bool guard,
+test_indoption(HeapTuple tuple, int attno, bool guard,
               int16 iopt_mask, int16 iopt_expect,
               bool *res)
 {
-   HeapTuple   tuple;
-   Form_pg_index rd_index PG_USED_FOR_ASSERTS_ONLY;
    Datum       datum;
    bool        isnull;
    int2vector *indoption;
@@ -127,14 +129,6 @@ test_indoption(Oid relid, int attno, bool guard,
        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);
@@ -144,8 +138,6 @@ test_indoption(Oid relid, int attno, bool guard,
 
    *res = (indoption_val & iopt_mask) == iopt_expect;
 
-   ReleaseSysCache(tuple);
-
    return true;
 }
 
@@ -195,9 +187,10 @@ indexam_property(FunctionCallInfo fcinfo,
    }
 
    /*
-    * 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.
+    * At this point, either index_oid == InvalidOid or it's a valid index OID.
+    * Also, after this test and the one below, 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();
@@ -224,80 +217,138 @@ indexam_property(FunctionCallInfo fcinfo,
 
    if (attno > 0)
    {
-       /* Handle column-level properties */
+       HeapTuple   tuple;
+       Form_pg_index rd_index;
+       bool        iskey = true;
+
+       /*
+        * Handle column-level properties. Many of these need the pg_index row
+        * (which we also need to use to check for nonkey atts) so we fetch
+        * that first.
+        */
+       tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));
+       if (!HeapTupleIsValid(tuple))
+           PG_RETURN_NULL();
+       rd_index = (Form_pg_index) GETSTRUCT(tuple);
+
+       Assert(index_oid == rd_index->indexrelid);
+       Assert(attno > 0 && attno <= rd_index->indnatts);
+
+       isnull = true;
+
+       /*
+        * If amcaninclude, we might be looking at an attno for a nonkey
+        * column, for which we (generically) assume that most properties are
+        * null.
+        */
+       if (routine->amcaninclude
+           && attno > rd_index->indnkeyatts)
+           iskey = false;
+
        switch (prop)
        {
            case AMPROP_ASC:
-               if (test_indoption(index_oid, attno, routine->amcanorder,
+               if (iskey &&
+                   test_indoption(tuple, attno, routine->amcanorder,
                                   INDOPTION_DESC, 0, &res))
-                   PG_RETURN_BOOL(res);
-               PG_RETURN_NULL();
+                   isnull = false;
+               break;
 
            case AMPROP_DESC:
-               if (test_indoption(index_oid, attno, routine->amcanorder,
+               if (iskey &&
+                   test_indoption(tuple, attno, routine->amcanorder,
                                   INDOPTION_DESC, INDOPTION_DESC, &res))
-                   PG_RETURN_BOOL(res);
-               PG_RETURN_NULL();
+                   isnull = false;
+               break;
 
            case AMPROP_NULLS_FIRST:
-               if (test_indoption(index_oid, attno, routine->amcanorder,
+               if (iskey &&
+                   test_indoption(tuple, attno, routine->amcanorder,
                                   INDOPTION_NULLS_FIRST, INDOPTION_NULLS_FIRST, &res))
-                   PG_RETURN_BOOL(res);
-               PG_RETURN_NULL();
+                   isnull = false;
+               break;
 
            case AMPROP_NULLS_LAST:
-               if (test_indoption(index_oid, attno, routine->amcanorder,
+               if (iskey &&
+                   test_indoption(tuple, attno, routine->amcanorder,
                                   INDOPTION_NULLS_FIRST, 0, &res))
-                   PG_RETURN_BOOL(res);
-               PG_RETURN_NULL();
+                   isnull = false;
+               break;
 
            case AMPROP_ORDERABLE:
-               PG_RETURN_BOOL(routine->amcanorder);
+               /*
+                * generic assumption is that nonkey columns are not orderable
+                */
+               res = iskey ? routine->amcanorder : false;
+               isnull = false;
+               break;
 
            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
+                * 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).
+                * 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, or if this is a
+                * nonkey column, and null otherwise (meaning we don't know).
                 */
-               if (!routine->amcanorderbyop)
-                   PG_RETURN_BOOL(false);
-               PG_RETURN_NULL();
+               if (!iskey || !routine->amcanorderbyop)
+               {
+                   res = false;
+                   isnull = false;
+               }
+               break;
 
            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.
-                */
+               /* note that we ignore iskey for this property */
+
+               isnull = false;
+               res = false;
+
+               if (routine->amcanreturn)
                {
+                   /*
+                    * 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);
+               break;
 
            case AMPROP_SEARCH_ARRAY:
-               PG_RETURN_BOOL(routine->amsearcharray);
+               if (iskey)
+               {
+                   res = routine->amsearcharray;
+                   isnull = false;
+               }
+               break;
 
            case AMPROP_SEARCH_NULLS:
-               PG_RETURN_BOOL(routine->amsearchnulls);
+               if (iskey)
+               {
+                   res = routine->amsearchnulls;
+                   isnull = false;
+               }
+               break;
 
            default:
-               PG_RETURN_NULL();
+               break;
        }
+
+       ReleaseSysCache(tuple);
+
+       if (!isnull)
+           PG_RETURN_BOOL(res);
+       PG_RETURN_NULL();
    }
 
    if (OidIsValid(index_oid))
@@ -344,6 +395,9 @@ indexam_property(FunctionCallInfo fcinfo,
        case AMPROP_CAN_EXCLUDE:
            PG_RETURN_BOOL(routine->amgettuple ? true : false);
 
+       case AMPROP_CAN_INCLUDE:
+           PG_RETURN_BOOL(routine->amcaninclude);
+
        default:
            PG_RETURN_NULL();
    }
index d16fa6823b6b837fa07ff999c1b388dafa028e3f..14526a6bb2c7c84500678194eb3c5b2396bbb8af 100644 (file)
@@ -50,7 +50,8 @@ typedef enum IndexAMProperty
    AMPROP_CAN_ORDER,           /* AM properties */
    AMPROP_CAN_UNIQUE,
    AMPROP_CAN_MULTI_COL,
-   AMPROP_CAN_EXCLUDE
+   AMPROP_CAN_EXCLUDE,
+   AMPROP_CAN_INCLUDE
 } IndexAMProperty;
 
 
@@ -196,6 +197,12 @@ typedef struct IndexAmRoutine
    /* type of data stored in index, or InvalidOid if variable */
    Oid         amkeytype;
 
+   /*
+    * If you add new properties to either the above or the below lists, then
+    * they should also (usually) be exposed via the property API (see
+    * IndexAMProperty at the top of the file, and utils/adt/amutils.c).
+    */
+
    /* interface functions */
    ambuild_function ambuild;
    ambuildempty_function ambuildempty;
index 74f7c9f1fd7be2fbfa9f491670328e5ad2f114f2..24cd3c5e2e4a4936b0f59bbdc07806a43008fe45 100644 (file)
@@ -12,7 +12,7 @@ select prop,
                     'clusterable', 'index_scan', 'bitmap_scan',
                     'backward_scan',
                     'can_order', 'can_unique', 'can_multi_col',
-                    'can_exclude',
+                    'can_exclude', 'can_include',
                     'bogus']::text[])
          with ordinality as u(prop,ord)
  where a.amname = 'btree'
@@ -36,8 +36,9 @@ select prop,
  can_unique         | t  |       | 
  can_multi_col      | t  |       | 
  can_exclude        | t  |       | 
+ can_include        | t  |       | 
  bogus              |    |       | 
-(18 rows)
+(19 rows)
 
 select prop,
        pg_indexam_has_property(a.oid, prop) as "AM",
@@ -50,7 +51,7 @@ select prop,
                     'clusterable', 'index_scan', 'bitmap_scan',
                     'backward_scan',
                     'can_order', 'can_unique', 'can_multi_col',
-                    'can_exclude',
+                    'can_exclude', 'can_include',
                     'bogus']::text[])
          with ordinality as u(prop,ord)
  where a.amname = 'gist'
@@ -74,8 +75,9 @@ select prop,
  can_unique         | f  |       | 
  can_multi_col      | t  |       | 
  can_exclude        | t  |       | 
+ can_include        | f  |       | 
  bogus              |    |       | 
-(18 rows)
+(19 rows)
 
 select prop,
        pg_index_column_has_property('onek_hundred'::regclass, 1, prop) as btree,
@@ -128,7 +130,7 @@ select prop,
 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[])
+                    'can_exclude', 'can_include', 'bogus']::text[])
          with ordinality as u(prop,ord)
  where amtype = 'i'
  order by amname, ord;
@@ -138,33 +140,39 @@ select amname, prop, pg_indexam_has_property(a.oid, prop) as p
  brin   | can_unique    | f
  brin   | can_multi_col | t
  brin   | can_exclude   | f
+ brin   | can_include   | f
  brin   | bogus         | 
  btree  | can_order     | t
  btree  | can_unique    | t
  btree  | can_multi_col | t
  btree  | can_exclude   | t
+ btree  | can_include   | t
  btree  | bogus         | 
  gin    | can_order     | f
  gin    | can_unique    | f
  gin    | can_multi_col | t
  gin    | can_exclude   | f
+ gin    | can_include   | f
  gin    | bogus         | 
  gist   | can_order     | f
  gist   | can_unique    | f
  gist   | can_multi_col | t
  gist   | can_exclude   | t
+ gist   | can_include   | f
  gist   | bogus         | 
  hash   | can_order     | f
  hash   | can_unique    | f
  hash   | can_multi_col | f
  hash   | can_exclude   | t
+ hash   | can_include   | f
  hash   | bogus         | 
  spgist | can_order     | f
  spgist | can_unique    | f
  spgist | can_multi_col | f
  spgist | can_exclude   | t
+ spgist | can_include   | f
  spgist | bogus         | 
-(30 rows)
+(36 rows)
 
 --
 -- additional checks for pg_index_column_has_property
@@ -206,3 +214,40 @@ select col, prop, pg_index_column_has_property(o, col, prop)
    4 | bogus       | 
 (24 rows)
 
+CREATE INDEX foocover ON foo (f1) INCLUDE (f2,f3);
+select col, prop, pg_index_column_has_property(o, col, prop)
+  from (values ('foocover'::regclass)) v1(o),
+       (values (1,'orderable'),(2,'asc'),(3,'desc'),
+               (4,'nulls_first'),(5,'nulls_last'),
+               (6,'distance_orderable'),(7,'returnable'),
+               (8, 'bogus')) v2(idx,prop),
+       generate_series(1,3) col
+ order by col, idx;
+ col |        prop        | pg_index_column_has_property 
+-----+--------------------+------------------------------
+   1 | orderable          | t
+   1 | asc                | t
+   1 | desc               | f
+   1 | nulls_first        | f
+   1 | nulls_last         | t
+   1 | distance_orderable | f
+   1 | returnable         | t
+   1 | bogus              | 
+   2 | orderable          | f
+   2 | asc                | 
+   2 | desc               | 
+   2 | nulls_first        | 
+   2 | nulls_last         | 
+   2 | distance_orderable | f
+   2 | returnable         | t
+   2 | bogus              | 
+   3 | orderable          | f
+   3 | asc                | 
+   3 | desc               | 
+   3 | nulls_first        | 
+   3 | nulls_last         | 
+   3 | distance_orderable | f
+   3 | returnable         | t
+   3 | bogus              | 
+(24 rows)
+
index cec1dcb53b648b04b9e87133afd682348ffdd5e2..8ca85ecf00696aa6a711885aaffd853368b2da14 100644 (file)
@@ -13,7 +13,7 @@ select prop,
                     'clusterable', 'index_scan', 'bitmap_scan',
                     'backward_scan',
                     'can_order', 'can_unique', 'can_multi_col',
-                    'can_exclude',
+                    'can_exclude', 'can_include',
                     'bogus']::text[])
          with ordinality as u(prop,ord)
  where a.amname = 'btree'
@@ -30,7 +30,7 @@ select prop,
                     'clusterable', 'index_scan', 'bitmap_scan',
                     'backward_scan',
                     'can_order', 'can_unique', 'can_multi_col',
-                    'can_exclude',
+                    'can_exclude', 'can_include',
                     'bogus']::text[])
          with ordinality as u(prop,ord)
  where a.amname = 'gist'
@@ -66,7 +66,7 @@ select prop,
 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[])
+                    'can_exclude', 'can_include', 'bogus']::text[])
          with ordinality as u(prop,ord)
  where amtype = 'i'
  order by amname, ord;
@@ -85,3 +85,14 @@ select col, prop, pg_index_column_has_property(o, col, prop)
                (6, 'bogus')) v2(idx,prop),
        generate_series(1,4) col
  order by col, idx;
+
+CREATE INDEX foocover ON foo (f1) INCLUDE (f2,f3);
+
+select col, prop, pg_index_column_has_property(o, col, prop)
+  from (values ('foocover'::regclass)) v1(o),
+       (values (1,'orderable'),(2,'asc'),(3,'desc'),
+               (4,'nulls_first'),(5,'nulls_last'),
+               (6,'distance_orderable'),(7,'returnable'),
+               (8, 'bogus')) v2(idx,prop),
+       generate_series(1,3) col
+ order by col, idx;