]> granicus.if.org Git - postgresql/commitdiff
Allow SET STATISTICS on expression indexes
authorSimon Riggs <simon@2ndQuadrant.com>
Wed, 6 Sep 2017 20:46:01 +0000 (13:46 -0700)
committerSimon Riggs <simon@2ndQuadrant.com>
Wed, 6 Sep 2017 20:46:01 +0000 (13:46 -0700)
Index columns are referenced by ordinal number rather than name, e.g.
CREATE INDEX coord_idx ON measured (x, y, (z + t));
ALTER INDEX coord_idx ALTER COLUMN 3 SET STATISTICS 1000;

Incompatibility note for release notes:
\d+ for indexes now also displays Stats Target

Authors: Alexander Korotkov, with contribution by Adrien NAYRAT
Review: Adrien NAYRAT, Simon Riggs
Wordsmith: Simon Riggs

13 files changed:
doc/src/sgml/ref/alter_index.sgml
src/backend/commands/tablecmds.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/parser/gram.y
src/backend/utils/cache/syscache.c
src/bin/psql/describe.c
src/bin/psql/tab-complete.c
src/include/nodes/parsenodes.h
src/include/utils/syscache.h
src/test/regress/expected/alter_table.out
src/test/regress/expected/create_index.out
src/test/regress/sql/alter_table.sql

index ad77b5743ac197911dc817395a3fb1a763b698ab..7d6553d2db1af937fc4223aca4059f7142d12257 100644 (file)
@@ -26,6 +26,8 @@ ALTER INDEX [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> SET
 ALTER INDEX <replaceable class="PARAMETER">name</replaceable> DEPENDS ON EXTENSION <replaceable class="PARAMETER">extension_name</replaceable>
 ALTER INDEX [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> SET ( <replaceable class="PARAMETER">storage_parameter</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
 ALTER INDEX [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> RESET ( <replaceable class="PARAMETER">storage_parameter</replaceable> [, ... ] )
+ALTER INDEX [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> ALTER [ COLUMN ] <replaceable class="PARAMETER">column_number</replaceable>
+    SET STATISTICS <replaceable class="PARAMETER">integer</replaceable>
 ALTER INDEX ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> [ OWNED BY <replaceable class="PARAMETER">role_name</replaceable> [, ... ] ]
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable> [ NOWAIT ]
 </synopsis>
@@ -110,6 +112,25 @@ ALTER INDEX ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ALTER [ COLUMN ] <replaceable class="PARAMETER">column_number</replaceable> SET STATISTICS <replaceable class="PARAMETER">integer</replaceable></literal></term>
+    <listitem>
+     <para>
+      This form sets the per-column statistics-gathering target for
+      subsequent <xref linkend="sql-analyze"> operations, though can
+      be used only on index columns that are defined as an expression.
+      Since expressions lack a unique name, we refer to them using the
+      ordinal number of the index column. 
+      The target can be set in the range 0 to 10000; alternatively, set it
+      to -1 to revert to using the system default statistics
+      target (<xref linkend="guc-default-statistics-target">).
+      For more information on the use of statistics by the
+      <productname>PostgreSQL</productname> query planner, refer to
+      <xref linkend="planner-stats">.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
@@ -130,6 +151,16 @@ ALTER INDEX ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">column_number</replaceable></term>
+      <listitem>
+       <para>
+        The ordinal number refers to the ordinal (left-to-right) position
+        of the index column.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="PARAMETER">name</replaceable></term>
       <listitem>
@@ -235,6 +266,14 @@ ALTER INDEX distributors SET (fillfactor = 75);
 REINDEX INDEX distributors;
 </programlisting></para>
 
+  <para>
+   Set the statistics-gathering target for an expression index:
+<programlisting>
+CREATE INDEX coord_idx ON measured (x, y, (z + t));
+ALTER INDEX coord_idx ALTER COLUMN 3 SET STATISTICS 1000;
+</programlisting>
+  </para>
+
  </refsect1>
 
  <refsect1>
index 0f08245a677c949db390d66e35a8e4493dc751c3..c8fc9cb7fe7832d39e3c1a0234af750d2b3faa69 100644 (file)
@@ -375,9 +375,9 @@ static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName,
 static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName,
                                  Node *def, LOCKMODE lockmode);
 static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode);
-static void ATPrepSetStatistics(Relation rel, const char *colName,
+static void ATPrepSetStatistics(Relation rel, const char *colName, int16 colNum,
                                        Node *newValue, LOCKMODE lockmode);
-static ObjectAddress ATExecSetStatistics(Relation rel, const char *colName,
+static ObjectAddress ATExecSetStatistics(Relation rel, const char *colName, int16 colNum,
                                        Node *newValue, LOCKMODE lockmode);
 static ObjectAddress ATExecSetOptions(Relation rel, const char *colName,
                                 Node *options, bool isReset, LOCKMODE lockmode);
@@ -3525,7 +3525,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
                case AT_SetStatistics:  /* ALTER COLUMN SET STATISTICS */
                        ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
                        /* Performs own permission checks */
-                       ATPrepSetStatistics(rel, cmd->name, cmd->def, lockmode);
+                       ATPrepSetStatistics(rel, cmd->name, cmd->num, cmd->def, lockmode);
                        pass = AT_PASS_MISC;
                        break;
                case AT_SetOptions:             /* ALTER COLUMN SET ( options ) */
@@ -3848,7 +3848,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
                        address = ATExecSetNotNull(tab, rel, cmd->name, lockmode);
                        break;
                case AT_SetStatistics:  /* ALTER COLUMN SET STATISTICS */
-                       address = ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode);
+                       address = ATExecSetStatistics(rel, cmd->name, cmd->num, cmd->def, lockmode);
                        break;
                case AT_SetOptions:             /* ALTER COLUMN SET ( options ) */
                        address = ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode);
@@ -6120,7 +6120,7 @@ ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE
  * ALTER TABLE ALTER COLUMN SET STATISTICS
  */
 static void
-ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode)
+ATPrepSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newValue, LOCKMODE lockmode)
 {
        /*
         * We do our own permission checking because (a) we want to allow SET
@@ -6138,6 +6138,15 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
                                 errmsg("\"%s\" is not a table, materialized view, index, or foreign table",
                                                RelationGetRelationName(rel))));
 
+       /*
+        * We allow referencing columns by numbers only for indexes, since
+        * table column numbers could contain gaps if columns are later dropped.
+        */
+       if (rel->rd_rel->relkind != RELKIND_INDEX && !colName)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("cannot refer to non-index column by number")));
+
        /* Permissions checks */
        if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
                aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
@@ -6148,7 +6157,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
  * Return value is the address of the modified column
  */
 static ObjectAddress
-ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode)
+ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newValue, LOCKMODE lockmode)
 {
        int                     newtarget;
        Relation        attrelation;
@@ -6181,13 +6190,27 @@ ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 
        attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
 
-       tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
+       if (colName)
+       {
+               tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
+
+               if (!HeapTupleIsValid(tuple))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                        errmsg("column \"%s\" of relation \"%s\" does not exist",
+                                                       colName, RelationGetRelationName(rel))));
+       }
+       else
+       {
+               tuple = SearchSysCacheCopyAttNum(RelationGetRelid(rel), colNum);
+
+               if (!HeapTupleIsValid(tuple))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                        errmsg("column number %d of relation \"%s\" does not exist",
+                                                       colNum, RelationGetRelationName(rel))));
+       }
 
-       if (!HeapTupleIsValid(tuple))
-               ereport(ERROR,
-                               (errcode(ERRCODE_UNDEFINED_COLUMN),
-                                errmsg("column \"%s\" of relation \"%s\" does not exist",
-                                               colName, RelationGetRelationName(rel))));
        attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
 
        attnum = attrtuple->attnum;
@@ -6197,6 +6220,14 @@ ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
                                 errmsg("cannot alter system column \"%s\"",
                                                colName)));
 
+       if (rel->rd_rel->relkind == RELKIND_INDEX &&
+               rel->rd_index->indkey.values[attnum - 1] != 0)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("cannot alter statistics on non-expression column \"%s\" of index \"%s\"",
+                                               NameStr(attrtuple->attname), RelationGetRelationName(rel)),
+                                errhint("Alter statistics on table column instead.")));
+
        attrtuple->attstattarget = newtarget;
 
        CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
index f9ddf4ed76589e3bc5d2f1e94fdc2f05f77be976..9bae2647fd1c80fb8f1e783f55b0cd18cbb0243a 100644 (file)
@@ -3087,6 +3087,7 @@ _copyAlterTableCmd(const AlterTableCmd *from)
 
        COPY_SCALAR_FIELD(subtype);
        COPY_STRING_FIELD(name);
+       COPY_SCALAR_FIELD(num);
        COPY_NODE_FIELD(newowner);
        COPY_NODE_FIELD(def);
        COPY_SCALAR_FIELD(behavior);
index 8d92c03633f96440a8b95b809c45963c16818958..11731da80ab3bb9d515a57200e2dcf438d83179d 100644 (file)
@@ -1098,6 +1098,7 @@ _equalAlterTableCmd(const AlterTableCmd *a, const AlterTableCmd *b)
 {
        COMPARE_SCALAR_FIELD(subtype);
        COMPARE_STRING_FIELD(name);
+       COMPARE_SCALAR_FIELD(num);
        COMPARE_NODE_FIELD(newowner);
        COMPARE_NODE_FIELD(def);
        COMPARE_SCALAR_FIELD(behavior);
index 7d0de99baf2ceda667d3c201f1001096181e367f..5eb398118e5c050d4b1898e036bbf2e4a75b1116 100644 (file)
@@ -2078,6 +2078,22 @@ alter_table_cmd:
                                        n->def = (Node *) makeInteger($6);
                                        $$ = (Node *)n;
                                }
+                       /* ALTER TABLE <name> ALTER [COLUMN] <colnum> SET STATISTICS <SignedIconst> */
+                       | ALTER opt_column Iconst SET STATISTICS SignedIconst
+                               {
+                                       AlterTableCmd *n = makeNode(AlterTableCmd);
+
+                                       if ($3 <= 0 || $3 > PG_INT16_MAX)
+                                               ereport(ERROR,
+                                                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                                                errmsg("column number must be in range from 1 to %d", PG_INT16_MAX),
+                                                                parser_errposition(@3)));
+
+                                       n->subtype = AT_SetStatistics;
+                                       n->num = (int16) $3;
+                                       n->def = (Node *) makeInteger($6);
+                                       $$ = (Node *)n;
+                               }
                        /* ALTER TABLE <name> ALTER [COLUMN] <colname> SET ( column_parameter = value [, ... ] ) */
                        | ALTER opt_column ColId SET reloptions
                                {
index 607fe9db79b47510710cc9430d47d56567067a31..fcbb683a991a4c8af64c0fa7434e9bf6d0b7a303 100644 (file)
@@ -1256,6 +1256,52 @@ SearchSysCacheExistsAttName(Oid relid, const char *attname)
 }
 
 
+/*
+ * SearchSysCacheAttNum
+ *
+ * This routine is equivalent to SearchSysCache on the ATTNUM cache,
+ * except that it will return NULL if the found attribute is marked
+ * attisdropped.  This is convenient for callers that want to act as
+ * though dropped attributes don't exist.
+ */
+HeapTuple
+SearchSysCacheAttNum(Oid relid, int16 attnum)
+{
+       HeapTuple       tuple;
+
+       tuple = SearchSysCache2(ATTNUM,
+                                                       ObjectIdGetDatum(relid),
+                                                       Int16GetDatum(attnum));
+       if (!HeapTupleIsValid(tuple))
+               return NULL;
+       if (((Form_pg_attribute) GETSTRUCT(tuple))->attisdropped)
+       {
+               ReleaseSysCache(tuple);
+               return NULL;
+       }
+       return tuple;
+}
+
+/*
+ * SearchSysCacheCopyAttNum
+ *
+ * As above, an attisdropped-aware version of SearchSysCacheCopy.
+ */
+HeapTuple
+SearchSysCacheCopyAttNum(Oid relid, int16 attnum)
+{
+       HeapTuple       tuple,
+                               newtuple;
+
+       tuple = SearchSysCacheAttNum(relid, attnum);
+       if (!HeapTupleIsValid(tuple))
+               return NULL;
+       newtuple = heap_copytuple(tuple);
+       ReleaseSysCache(tuple);
+       return newtuple;
+}
+
+
 /*
  * SysCacheGetAttr
  *
index f6049cc9e5c13fda23a55a2d7a3acbd490b15ff4..6fb9bdd063583fb8b60ad282aeb5256df67942e4 100644 (file)
@@ -1742,6 +1742,7 @@ describeOneTableDetails(const char *schemaname,
        {
                headers[cols++] = gettext_noop("Storage");
                if (tableinfo.relkind == RELKIND_RELATION ||
+                       tableinfo.relkind == RELKIND_INDEX ||
                        tableinfo.relkind == RELKIND_MATVIEW ||
                        tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
                        tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
@@ -1841,6 +1842,7 @@ describeOneTableDetails(const char *schemaname,
 
                        /* Statistics target, if the relkind supports this feature */
                        if (tableinfo.relkind == RELKIND_RELATION ||
+                               tableinfo.relkind == RELKIND_INDEX ||
                                tableinfo.relkind == RELKIND_MATVIEW ||
                                tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
                                tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
index 7959f9ac16ec820b778b370e17240ff64330c133..2ab8809fa590064f23a3e84ce205fb3dfee0cb97 100644 (file)
@@ -1644,7 +1644,10 @@ psql_completion(const char *text, int start, int end)
                                                                   "UNION SELECT 'ALL IN TABLESPACE'");
        /* ALTER INDEX <name> */
        else if (Matches3("ALTER", "INDEX", MatchAny))
-               COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
+               COMPLETE_WITH_LIST5("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET", "RESET");
+       /* ALTER INDEX <name> ALTER COLUMN <colnum> */
+       else if (Matches6("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN", MatchAny))
+               COMPLETE_WITH_CONST("SET STATISTICS");
        /* ALTER INDEX <name> SET */
        else if (Matches4("ALTER", "INDEX", MatchAny, "SET"))
                COMPLETE_WITH_LIST2("(", "TABLESPACE");
index ef6753e31ad4945620c967a9f486b151d1cf4a7c..3171815320c4f5c082ce7255acd1c649a2e21852 100644 (file)
@@ -1777,6 +1777,8 @@ typedef struct AlterTableCmd      /* one subcommand of an ALTER TABLE */
        AlterTableType subtype;         /* Type of table alteration to apply */
        char       *name;                       /* column, constraint, or trigger to act on,
                                                                 * or tablespace */
+       int16           num;                    /* attribute number for columns referenced
+                                                                * by number */
        RoleSpec   *newowner;
        Node       *def;                        /* definition of new column, index,
                                                                 * constraint, or parent table */
index 8352b40f4e27996e3f941485fb3084a19cf23eb7..8a92ea27acd60a50acf8d401ddf619c568e62a66 100644 (file)
@@ -131,6 +131,9 @@ extern HeapTuple SearchSysCacheAttName(Oid relid, const char *attname);
 extern HeapTuple SearchSysCacheCopyAttName(Oid relid, const char *attname);
 extern bool SearchSysCacheExistsAttName(Oid relid, const char *attname);
 
+extern HeapTuple SearchSysCacheAttNum(Oid relid, int16 attnum);
+extern HeapTuple SearchSysCacheCopyAttNum(Oid relid, int16 attnum);
+
 extern Datum SysCacheGetAttr(int cacheId, HeapTuple tup,
                                AttrNumber attributeNumber, bool *isNull);
 
index ed03cb9c630a40f04782c2cf653f156bc0b7ffc2..0f3642316359844824bc587525c9e0e994a63e99 100644 (file)
@@ -94,6 +94,30 @@ SELECT * FROM tmp;
          | 4 | name | text | 4.1 | 4.1 | 2 | ((4.1,4.1),(3.1,3.1)) | Mon May 01 00:30:30 1995 PDT | c | {"Mon May 01 00:30:30 1995 PDT","Mon Aug 24 14:43:07 1992 PDT","Wed Dec 31 16:00:00 1969 PST"} | 314159 | (1,1) | 512 | 1 2 3 4 5 6 7 8 | magnetic disk | (1.1,1.1) | [(4.1,4.1),(3.1,3.1)] | ((0,2),(4.1,4.1),(3.1,3.1)) | (4.1,4.1),(3.1,3.1) | ["Wed Dec 31 16:00:00 1969 PST" "infinity"] | Thu Jan 01 00:00:00 1970 | @ 1 hour 10 secs | {1,2,3,4} | {1,2,3,4} | {1,2,3,4}
 (1 row)
 
+CREATE INDEX tmp_idx ON tmp (a, (d + e), b);
+ALTER INDEX tmp_idx ALTER COLUMN 0 SET STATISTICS 1000;
+ERROR:  column number must be in range from 1 to 32767
+LINE 1: ALTER INDEX tmp_idx ALTER COLUMN 0 SET STATISTICS 1000;
+                                         ^
+ALTER INDEX tmp_idx ALTER COLUMN 1 SET STATISTICS 1000;
+ERROR:  cannot alter statistics on non-expression column "a" of index "tmp_idx"
+HINT:  Alter statistics on table column instead.
+ALTER INDEX tmp_idx ALTER COLUMN 2 SET STATISTICS 1000;
+\d+ tmp_idx
+                     Index "public.tmp_idx"
+ Column |       Type       | Definition | Storage | Stats target 
+--------+------------------+------------+---------+--------------
+ a      | integer          | a          | plain   | 
+ expr   | double precision | (d + e)    | plain   | 1000
+ b      | cstring          | b          | plain   | 
+btree, for table "public.tmp"
+
+ALTER INDEX tmp_idx ALTER COLUMN 3 SET STATISTICS 1000;
+ERROR:  cannot alter statistics on non-expression column "b" of index "tmp_idx"
+HINT:  Alter statistics on table column instead.
+ALTER INDEX tmp_idx ALTER COLUMN 4 SET STATISTICS 1000;
+ERROR:  column number 4 of relation "tmp_idx" does not exist
+ALTER INDEX tmp_idx ALTER COLUMN 2 SET STATISTICS -1;
 DROP TABLE tmp;
 --
 -- rename - check on both non-temp and temp tables
index 064adb4640bc3c99b7f73df7ae696192bfc86f8a..8450f2463e859ea0e8154bdd7224b6784ba28ee8 100644 (file)
@@ -2324,10 +2324,10 @@ DROP TABLE array_gin_test;
 CREATE INDEX gin_relopts_test ON array_index_op_test USING gin (i)
   WITH (FASTUPDATE=on, GIN_PENDING_LIST_LIMIT=128);
 \d+ gin_relopts_test
-     Index "public.gin_relopts_test"
- Column |  Type   | Definition | Storage 
---------+---------+------------+---------
- i      | integer | i          | plain
+            Index "public.gin_relopts_test"
+ Column |  Type   | Definition | Storage | Stats target 
+--------+---------+------------+---------+--------------
+ i      | integer | i          | plain   | 
 gin, for table "public.array_index_op_test"
 Options: fastupdate=on, gin_pending_list_limit=128
 
index 9a20dd141a2543df9749ea6d29d30d7152e0bf10..e6f6669880b96c0142d22b9d7d8c872d8366be15 100644 (file)
@@ -142,6 +142,22 @@ INSERT INTO tmp (a, b, c, d, e, f, g, h, i, j, k, l, m, n, p, q, r, s, t, u,
 
 SELECT * FROM tmp;
 
+CREATE INDEX tmp_idx ON tmp (a, (d + e), b);
+
+ALTER INDEX tmp_idx ALTER COLUMN 0 SET STATISTICS 1000;
+
+ALTER INDEX tmp_idx ALTER COLUMN 1 SET STATISTICS 1000;
+
+ALTER INDEX tmp_idx ALTER COLUMN 2 SET STATISTICS 1000;
+
+\d+ tmp_idx
+
+ALTER INDEX tmp_idx ALTER COLUMN 3 SET STATISTICS 1000;
+
+ALTER INDEX tmp_idx ALTER COLUMN 4 SET STATISTICS 1000;
+
+ALTER INDEX tmp_idx ALTER COLUMN 2 SET STATISTICS -1;
+
 DROP TABLE tmp;