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
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>
</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>
</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>
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>
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);
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 ) */
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);
* 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
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,
* 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;
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;
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);
COPY_SCALAR_FIELD(subtype);
COPY_STRING_FIELD(name);
+ COPY_SCALAR_FIELD(num);
COPY_NODE_FIELD(newowner);
COPY_NODE_FIELD(def);
COPY_SCALAR_FIELD(behavior);
{
COMPARE_SCALAR_FIELD(subtype);
COMPARE_STRING_FIELD(name);
+ COMPARE_SCALAR_FIELD(num);
COMPARE_NODE_FIELD(newowner);
COMPARE_NODE_FIELD(def);
COMPARE_SCALAR_FIELD(behavior);
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
{
}
+/*
+ * 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
*
{
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)
/* 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)
"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");
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 */
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);
| 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
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
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;