diff --git a/doc/src/sgml/ref/alter_index.sgml b/doc/src/sgml/ref/alter_index.sgml new file mode 100644 index ad77b57..34ab710 *** a/doc/src/sgml/ref/alter_index.sgml --- b/doc/src/sgml/ref/alter_index.sgml *************** ALTER INDEX [ IF EXISTS ] name DEPENDS ON EXTENSION extension_name ALTER INDEX [ IF EXISTS ] name SET ( storage_parameter = value [, ... ] ) ALTER INDEX [ IF EXISTS ] name RESET ( storage_parameter [, ... ] ) + ALTER INDEX [ IF EXISTS ] name ALTER [ COLUMN ] column_number + SET STATISTICS integer ALTER INDEX ALL IN TABLESPACE name [ OWNED BY role_name [, ... ] ] SET TABLESPACE new_tablespace [ NOWAIT ] *************** ALTER INDEX ALL IN TABLESPACE + + ALTER [ COLUMN ] column_number SET STATISTICS integer + + + This form sets statistics-gathering target for an indexed expression + referenced by its number. This value is used in subsequent + operations. + 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 (). + For more information on the use of statistics by the + PostgreSQL query planner, refer to + . + + + + *************** ALTER INDEX ALL IN TABLESPACE + column_number + + + The number of index column starting from 1. + + + + + name *************** ALTER INDEX distributors SET (fillfactor *** 235,240 **** --- 263,276 ---- REINDEX INDEX distributors; + + To set statistics-gathering target for an indexed expression: + + CREATE INDEX coord_idx ON measured (x, y, (z + t)); + ALTER INDEX coord_idx ALTER COLUMN 3 SET STATISTICS 1000; + + + diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c new file mode 100644 index 7959120..90fb5d7 *** a/src/backend/commands/tablecmds.c --- b/src/backend/commands/tablecmds.c *************** static ObjectAddress ATExecAddIdentity(R *** 367,375 **** 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, Node *newValue, LOCKMODE lockmode); ! static ObjectAddress ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode); static ObjectAddress ATExecSetOptions(Relation rel, const char *colName, Node *options, bool isReset, LOCKMODE lockmode); --- 367,375 ---- 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, int16 colNum, Node *newValue, LOCKMODE lockmode); ! 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); *************** ATPrepCmd(List **wqueue, Relation rel, A *** 3516,3522 **** case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */ ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); /* Performs own permission checks */ ! ATPrepSetStatistics(rel, cmd->name, cmd->def, lockmode); pass = AT_PASS_MISC; break; case AT_SetOptions: /* ALTER COLUMN SET ( options ) */ --- 3516,3522 ---- case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */ ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); /* Performs own permission checks */ ! ATPrepSetStatistics(rel, cmd->name, cmd->num, cmd->def, lockmode); pass = AT_PASS_MISC; break; case AT_SetOptions: /* ALTER COLUMN SET ( options ) */ *************** ATExecCmd(List **wqueue, AlteredTableInf *** 3840,3846 **** address = ATExecSetNotNull(tab, rel, cmd->name, lockmode); break; case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */ ! address = ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode); break; case AT_SetOptions: /* ALTER COLUMN SET ( options ) */ address = ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode); --- 3840,3846 ---- address = ATExecSetNotNull(tab, rel, cmd->name, lockmode); break; case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */ ! 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); *************** ATExecDropIdentity(Relation rel, const c *** 6095,6101 **** * ALTER TABLE ALTER COLUMN SET STATISTICS */ static void ! ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode) { /* * We do our own permission checking because (a) we want to allow SET --- 6095,6101 ---- * ALTER TABLE ALTER COLUMN SET STATISTICS */ static void ! 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 *************** ATPrepSetStatistics(Relation rel, const *** 6113,6118 **** --- 6113,6128 ---- errmsg("\"%s\" is not a table, materialized view, index, or foreign table", RelationGetRelationName(rel)))); + /* + * We do allow referencing columns by names only for indexes, because + * table column numbers could contain gaps. + */ + if (rel->rd_rel->relkind != RELKIND_INDEX && !colName) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a index, but only index columns could be refered by number", + RelationGetRelationName(rel)))); + /* Permissions checks */ if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, *************** ATPrepSetStatistics(Relation rel, const *** 6123,6129 **** * Return value is the address of the modified column */ static ObjectAddress ! ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode) { int newtarget; Relation attrelation; --- 6133,6139 ---- * Return value is the address of the modified column */ static ObjectAddress ! ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newValue, LOCKMODE lockmode) { int newtarget; Relation attrelation; *************** ATExecSetStatistics(Relation rel, const *** 6156,6168 **** attrelation = heap_open(AttributeRelationId, RowExclusiveLock); ! 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)))); attrtuple = (Form_pg_attribute) GETSTRUCT(tuple); attnum = attrtuple->attnum; --- 6166,6192 ---- attrelation = heap_open(AttributeRelationId, RowExclusiveLock); ! 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)))); ! } attrtuple = (Form_pg_attribute) GETSTRUCT(tuple); attnum = attrtuple->attnum; *************** ATExecSetStatistics(Relation rel, const *** 6172,6177 **** --- 6196,6209 ---- 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); diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c new file mode 100644 index 36bf1dc..5ce2285 *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** _copyAlterTableCmd(const AlterTableCmd * *** 3082,3087 **** --- 3082,3088 ---- COPY_SCALAR_FIELD(subtype); COPY_STRING_FIELD(name); + COPY_SCALAR_FIELD(num); COPY_NODE_FIELD(newowner); COPY_NODE_FIELD(def); COPY_SCALAR_FIELD(behavior); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c new file mode 100644 index 5bcf031..0b6fc7c *** a/src/backend/nodes/equalfuncs.c --- b/src/backend/nodes/equalfuncs.c *************** _equalAlterTableCmd(const AlterTableCmd *** 1098,1103 **** --- 1098,1104 ---- { COMPARE_SCALAR_FIELD(subtype); COMPARE_STRING_FIELD(name); + COMPARE_SCALAR_FIELD(num); COMPARE_NODE_FIELD(newowner); COMPARE_NODE_FIELD(def); COMPARE_SCALAR_FIELD(behavior); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y new file mode 100644 index 7e03624..2a34058 *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** alter_table_cmd: *** 2109,2114 **** --- 2109,2129 ---- n->def = (Node *) makeInteger($6); $$ = (Node *)n; } + /* ALTER TABLE ALTER [COLUMN] SET STATISTICS */ + | ALTER opt_column Iconst SET STATISTICS SignedIconst + { + 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))); + + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_SetStatistics; + n->num = (int16) $3; + n->def = (Node *) makeInteger($6); + $$ = (Node *)n; + } /* ALTER TABLE ALTER [COLUMN] SET ( column_parameter = value [, ... ] ) */ | ALTER opt_column ColId SET reloptions { diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c new file mode 100644 index e66bb03..ea41eb0 *** a/src/backend/utils/cache/syscache.c --- b/src/backend/utils/cache/syscache.c *************** SearchSysCacheExistsAttName(Oid relid, c *** 1257,1262 **** --- 1257,1308 ---- /* + * 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 tuple; + newtuple = heap_copytuple(tuple); + ReleaseSysCache(tuple); + return newtuple; + } + + + /* * SysCacheGetAttr * * Given a tuple previously fetched by SearchSysCache(), diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h new file mode 100644 index 8720e71..916ade4 *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** typedef struct AlterTableCmd /* one subc *** 1753,1758 **** --- 1753,1760 ---- 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 */ diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h new file mode 100644 index 246601c..87967b3 *** a/src/include/utils/syscache.h --- b/src/include/utils/syscache.h *************** extern HeapTuple SearchSysCacheAttName(O *** 131,136 **** --- 131,139 ---- 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); diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out new file mode 100644 index 8aadbb8..cee919d *** a/src/test/regress/expected/alter_table.out --- b/src/test/regress/expected/alter_table.out *************** SELECT * FROM tmp; *** 94,99 **** --- 94,114 ---- | 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; + 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 diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql new file mode 100644 index c41b487..b61bb01 *** a/src/test/regress/sql/alter_table.sql --- b/src/test/regress/sql/alter_table.sql *************** INSERT INTO tmp (a, b, c, d, e, f, g, h, *** 142,147 **** --- 142,161 ---- 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; + + 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;