diff options
-rw-r--r-- | doc/src/sgml/perform.sgml | 8 | ||||
-rw-r--r-- | doc/src/sgml/planstats.sgml | 4 | ||||
-rw-r--r-- | doc/src/sgml/ref/alter_statistics.sgml | 28 | ||||
-rw-r--r-- | doc/src/sgml/ref/create_statistics.sgml | 85 | ||||
-rw-r--r-- | doc/src/sgml/ref/drop_statistics.sgml | 16 | ||||
-rw-r--r-- | src/backend/commands/statscmds.c | 109 | ||||
-rw-r--r-- | src/backend/nodes/copyfuncs.c | 6 | ||||
-rw-r--r-- | src/backend/nodes/equalfuncs.c | 6 | ||||
-rw-r--r-- | src/backend/nodes/outfuncs.c | 6 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 42 | ||||
-rw-r--r-- | src/backend/utils/adt/ruleutils.c | 10 | ||||
-rw-r--r-- | src/bin/pg_dump/t/002_pg_dump.pl | 10 | ||||
-rw-r--r-- | src/bin/psql/describe.c | 14 | ||||
-rw-r--r-- | src/bin/psql/tab-complete.c | 38 | ||||
-rw-r--r-- | src/include/nodes/parsenodes.h | 8 | ||||
-rw-r--r-- | src/test/regress/expected/alter_generic.out | 52 | ||||
-rw-r--r-- | src/test/regress/expected/object_address.out | 2 | ||||
-rw-r--r-- | src/test/regress/expected/stats_ext.out | 85 | ||||
-rw-r--r-- | src/test/regress/sql/alter_generic.sql | 12 | ||||
-rw-r--r-- | src/test/regress/sql/object_address.sql | 2 | ||||
-rw-r--r-- | src/test/regress/sql/stats_ext.sql | 64 |
21 files changed, 321 insertions, 286 deletions
diff --git a/doc/src/sgml/perform.sgml b/doc/src/sgml/perform.sgml index b10b734b901..32e17ee5f8e 100644 --- a/doc/src/sgml/perform.sgml +++ b/doc/src/sgml/perform.sgml @@ -1132,8 +1132,8 @@ WHERE tablename = 'road'; To inspect functional dependencies on a statistics <literal>stts</literal>, you may do this: <programlisting> -CREATE STATISTICS stts WITH (dependencies) - ON (zip, city) FROM zipcodes; +CREATE STATISTICS stts (dependencies) + ON zip, city FROM zipcodes; ANALYZE zipcodes; SELECT stxname, stxkeys, stxdependencies FROM pg_statistic_ext @@ -1219,8 +1219,8 @@ EXPLAIN (ANALYZE, TIMING OFF) SELECT * FROM t WHERE a = 1 AND b = 10; Continuing the above example, the n-distinct coefficients in a ZIP code table may look like the following: <programlisting> -CREATE STATISTICS stts2 WITH (ndistinct) - ON (zip, state, city) FROM zipcodes; +CREATE STATISTICS stts2 (ndistinct) + ON zip, state, city FROM zipcodes; ANALYZE zipcodes; SELECT stxkeys AS k, stxndistinct AS nd FROM pg_statistic_ext diff --git a/doc/src/sgml/planstats.sgml b/doc/src/sgml/planstats.sgml index 11580bfd228..ef847b96333 100644 --- a/doc/src/sgml/planstats.sgml +++ b/doc/src/sgml/planstats.sgml @@ -526,7 +526,7 @@ EXPLAIN (ANALYZE, TIMING OFF) SELECT * FROM t WHERE a = 1 AND b = 1; multivariate statistics on the two columns: <programlisting> -CREATE STATISTICS stts WITH (dependencies) ON (a, b) FROM t; +CREATE STATISTICS stts (dependencies) ON a, b FROM t; ANALYZE t; EXPLAIN (ANALYZE, TIMING OFF) SELECT * FROM t WHERE a = 1 AND b = 1; QUERY PLAN @@ -569,7 +569,7 @@ EXPLAIN (ANALYZE, TIMING OFF) SELECT COUNT(*) FROM t GROUP BY a, b; calculation, the estimate is much improved: <programlisting> DROP STATISTICS stts; -CREATE STATISTICS stts WITH (dependencies, ndistinct) ON (a, b) FROM t; +CREATE STATISTICS stts (dependencies, ndistinct) ON a, b FROM t; ANALYZE t; EXPLAIN (ANALYZE, TIMING OFF) SELECT COUNT(*) FROM t GROUP BY a, b; QUERY PLAN diff --git a/doc/src/sgml/ref/alter_statistics.sgml b/doc/src/sgml/ref/alter_statistics.sgml index 3e4d28614a2..4f256698529 100644 --- a/doc/src/sgml/ref/alter_statistics.sgml +++ b/doc/src/sgml/ref/alter_statistics.sgml @@ -17,7 +17,7 @@ PostgreSQL documentation <refnamediv> <refname>ALTER STATISTICS</refname> <refpurpose> - change the definition of a extended statistics + change the definition of an extended statistics object </refpurpose> </refnamediv> @@ -34,19 +34,20 @@ ALTER STATISTICS <replaceable class="parameter">name</replaceable> SET SCHEMA <r <para> <command>ALTER STATISTICS</command> changes the parameters of an existing - extended statistics. Any parameters not specifically set in the + extended statistics object. Any parameters not specifically set in the <command>ALTER STATISTICS</command> command retain their prior settings. </para> <para> - You must own the statistics to use <command>ALTER STATISTICS</>. - To change a statistics' schema, you must also have <literal>CREATE</> - privilege on the new schema. + You must own the statistics object to use <command>ALTER STATISTICS</>. + To change a statistics object's schema, you must also + have <literal>CREATE</> privilege on the new schema. To alter the owner, you must also be a direct or indirect member of the new owning role, and that role must have <literal>CREATE</literal> privilege on - the statistics' schema. (These restrictions enforce that altering the owner - doesn't do anything you couldn't do by dropping and recreating the statistics. - However, a superuser can alter ownership of any statistics anyway.) + the statistics object's schema. (These restrictions enforce that altering + the owner doesn't do anything you couldn't do by dropping and recreating + the statistics object. However, a superuser can alter ownership of any + statistics object anyway.) </para> </refsect1> @@ -59,7 +60,8 @@ ALTER STATISTICS <replaceable class="parameter">name</replaceable> SET SCHEMA <r <term><replaceable class="parameter">name</replaceable></term> <listitem> <para> - The name (optionally schema-qualified) of the statistics to be altered. + The name (optionally schema-qualified) of the statistics object to be + altered. </para> </listitem> </varlistentry> @@ -68,7 +70,7 @@ ALTER STATISTICS <replaceable class="parameter">name</replaceable> SET SCHEMA <r <term><replaceable class="PARAMETER">new_owner</replaceable></term> <listitem> <para> - The user name of the new owner of the statistics. + The user name of the new owner of the statistics object. </para> </listitem> </varlistentry> @@ -77,7 +79,7 @@ ALTER STATISTICS <replaceable class="parameter">name</replaceable> SET SCHEMA <r <term><replaceable class="parameter">new_name</replaceable></term> <listitem> <para> - The new name for the statistics. + The new name for the statistics object. </para> </listitem> </varlistentry> @@ -86,7 +88,7 @@ ALTER STATISTICS <replaceable class="parameter">name</replaceable> SET SCHEMA <r <term><replaceable class="parameter">new_schema</replaceable></term> <listitem> <para> - The new schema for the statistics. + The new schema for the statistics object. </para> </listitem> </varlistentry> @@ -99,7 +101,7 @@ ALTER STATISTICS <replaceable class="parameter">name</replaceable> SET SCHEMA <r <title>Compatibility</title> <para> - There's no <command>ALTER STATISTICS</command> command in the SQL standard. + There is no <command>ALTER STATISTICS</command> command in the SQL standard. </para> </refsect1> diff --git a/doc/src/sgml/ref/create_statistics.sgml b/doc/src/sgml/ref/create_statistics.sgml index edbcf5840bb..92ee4e4efa7 100644 --- a/doc/src/sgml/ref/create_statistics.sgml +++ b/doc/src/sgml/ref/create_statistics.sgml @@ -22,8 +22,8 @@ PostgreSQL documentation <refsynopsisdiv> <synopsis> CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="PARAMETER">statistics_name</replaceable> - WITH ( <replaceable class="PARAMETER">option</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) - ON ( <replaceable class="PARAMETER">column_name</replaceable>, <replaceable class="PARAMETER">column_name</replaceable> [, ...]) + [ ( <replaceable class="PARAMETER">statistic_type</replaceable> [, ... ] ) ] + ON <replaceable class="PARAMETER">column_name</replaceable>, <replaceable class="PARAMETER">column_name</replaceable> [, ...] FROM <replaceable class="PARAMETER">table_name</replaceable> </synopsis> @@ -34,17 +34,17 @@ CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="PARAMETER">statistics_na <para> <command>CREATE STATISTICS</command> will create a new extended statistics - object on the specified table, foreign table or materialized view. - The statistics will be created in the current database and - will be owned by the user issuing the command. + object tracking data about the specified table, foreign table or + materialized view. The statistics object will be created in the current + database and will be owned by the user issuing the command. </para> <para> If a schema name is given (for example, <literal>CREATE STATISTICS - myschema.mystat ...</>) then the statistics is created in the specified - schema. Otherwise it is created in the current schema. The name of - the statistics must be distinct from the name of any other statistics in the - same schema. + myschema.mystat ...</>) then the statistics object is created in the + specified schema. Otherwise it is created in the current schema. + The name of the statistics object must be distinct from the name of any + other statistics object in the same schema. </para> </refsect1> @@ -57,10 +57,10 @@ CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="PARAMETER">statistics_na <term><literal>IF NOT EXISTS</></term> <listitem> <para> - Do not throw an error if a statistics with the same name already exists. - A notice is issued in this case. Note that only the name of the - statistics object is considered here. The definition of the statistics is - not considered. + Do not throw an error if a statistics object with the same name already + exists. A notice is issued in this case. Note that only the name of + the statistics object is considered here, not the details of its + definition. </para> </listitem> </varlistentry> @@ -69,67 +69,45 @@ CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="PARAMETER">statistics_na <term><replaceable class="PARAMETER">statistics_name</replaceable></term> <listitem> <para> - The name (optionally schema-qualified) of the statistics to be created. + The name (optionally schema-qualified) of the statistics object to be + created. </para> </listitem> </varlistentry> <varlistentry> - <term><replaceable class="PARAMETER">column_name</replaceable></term> + <term><replaceable class="PARAMETER">statistic_type</replaceable></term> <listitem> <para> - The name of a column to be included in the statistics. + A statistic type to be computed in this statistics object. Currently + supported types are <literal>ndistinct</literal>, which enables + n-distinct coefficient tracking, + and <literal>dependencies</literal>, which enables functional + dependencies. </para> </listitem> </varlistentry> <varlistentry> - <term><replaceable class="PARAMETER">table_name</replaceable></term> - <listitem> - <para> - The name (optionally schema-qualified) of the table the statistics should - be created on. - </para> - </listitem> - </varlistentry> - - </variablelist> - - <refsect2 id="SQL-CREATESTATISTICS-parameters"> - <title id="SQL-CREATESTATISTICS-parameters-title">Parameters</title> - - <indexterm zone="sql-createstatistics-parameters"> - <primary>statistics parameters</primary> - </indexterm> - - <para> - The <literal>WITH</> clause can specify <firstterm>options</> - for the statistics. Available options are listed below. - </para> - - <variablelist> - - <varlistentry> - <term><literal>dependencies</> (<type>boolean</>)</term> + <term><replaceable class="PARAMETER">column_name</replaceable></term> <listitem> <para> - Enables functional dependencies for the statistics. + The name of a table column to be included in the statistics object. </para> </listitem> </varlistentry> <varlistentry> - <term><literal>ndistinct</> (<type>boolean</>)</term> + <term><replaceable class="PARAMETER">table_name</replaceable></term> <listitem> <para> - Enables ndistinct coefficients for the statistics. + The name (optionally schema-qualified) of the table containing the + column(s) the statistics are computed on. </para> </listitem> </varlistentry> - </variablelist> - - </refsect2> + </variablelist> </refsect1> <refsect1> @@ -158,7 +136,7 @@ CREATE TABLE t1 ( INSERT INTO t1 SELECT i/100, i/500 FROM generate_series(1,1000000) s(i); -CREATE STATISTICS s1 WITH (dependencies) ON (a, b) FROM t1; +CREATE STATISTICS s1 (dependencies) ON a, b FROM t1; ANALYZE t1; @@ -168,6 +146,11 @@ EXPLAIN ANALYZE SELECT * FROM t1 WHERE (a = 1) AND (b = 0); -- invalid combination of values EXPLAIN ANALYZE SELECT * FROM t1 WHERE (a = 1) AND (b = 1); </programlisting> + + Without functional-dependency statistics, the planner would make the + same estimate of the number of matching rows for these two queries. + With such statistics, it is able to tell that one case has matches + and the other does not. </para> </refsect1> @@ -176,7 +159,7 @@ EXPLAIN ANALYZE SELECT * FROM t1 WHERE (a = 1) AND (b = 1); <title>Compatibility</title> <para> - There's no <command>CREATE STATISTICS</command> command in the SQL standard. + There is no <command>CREATE STATISTICS</command> command in the SQL standard. </para> </refsect1> diff --git a/doc/src/sgml/ref/drop_statistics.sgml b/doc/src/sgml/ref/drop_statistics.sgml index 98c338182b4..ef659fca614 100644 --- a/doc/src/sgml/ref/drop_statistics.sgml +++ b/doc/src/sgml/ref/drop_statistics.sgml @@ -29,9 +29,9 @@ DROP STATISTICS [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> <title>Description</title> <para> - <command>DROP STATISTICS</command> removes statistics from the database. - Only the statistics owner, the schema owner, and superuser can drop a - statistics. + <command>DROP STATISTICS</command> removes statistics object(s) from the + database. Only the statistics object's owner, the schema owner, or a + superuser can drop a statistics object. </para> </refsect1> @@ -44,8 +44,8 @@ DROP STATISTICS [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> <term><literal>IF EXISTS</literal></term> <listitem> <para> - Do not throw an error if the statistics do not exist. A notice is - issued in this case. + Do not throw an error if the statistics object does not exist. A notice + is issued in this case. </para> </listitem> </varlistentry> @@ -54,7 +54,7 @@ DROP STATISTICS [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> <term><replaceable class="PARAMETER">name</replaceable></term> <listitem> <para> - The name (optionally schema-qualified) of the statistics to drop. + The name (optionally schema-qualified) of the statistics object to drop. </para> </listitem> </varlistentry> @@ -66,7 +66,7 @@ DROP STATISTICS [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> <title>Examples</title> <para> - To destroy two statistics objects on different schemas, without failing + To destroy two statistics objects in different schemas, without failing if they don't exist: <programlisting> @@ -82,7 +82,7 @@ DROP STATISTICS IF EXISTS <title>Compatibility</title> <para> - There's no <command>DROP STATISTICS</command> command in the SQL standard. + There is no <command>DROP STATISTICS</command> command in the SQL standard. </para> </refsect1> diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index 0b9c33e30a3..662b4fa15d2 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -60,7 +60,7 @@ CreateStatistics(CreateStatsStmt *stmt) bool nulls[Natts_pg_statistic_ext]; int2vector *stxkeys; Relation statrel; - Relation rel; + Relation rel = NULL; Oid relid; ObjectAddress parentobject, childobject; @@ -71,7 +71,7 @@ CreateStatistics(CreateStatsStmt *stmt) bool build_dependencies; bool requested_type = false; int i; - ListCell *l; + ListCell *cell; Assert(IsA(stmt, CreateStatsStmt)); @@ -101,35 +101,81 @@ CreateStatistics(CreateStatsStmt *stmt) } /* - * CREATE STATISTICS will influence future execution plans but does not - * interfere with currently executing plans. So it should be enough to - * take only ShareUpdateExclusiveLock on relation, conflicting with - * ANALYZE and other DDL that sets statistical information, but not with - * normal queries. + * Examine the FROM clause. Currently, we only allow it to be a single + * simple table, but later we'll probably allow multiple tables and JOIN + * syntax. The grammar is already prepared for that, so we have to check + * here that what we got is what we can support. */ - rel = relation_openrv(stmt->relation, ShareUpdateExclusiveLock); - relid = RelationGetRelid(rel); - - if (rel->rd_rel->relkind != RELKIND_RELATION && - rel->rd_rel->relkind != RELKIND_MATVIEW && - rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE && - rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + if (list_length(stmt->relations) != 1) ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("relation \"%s\" is not a table, foreign table, or materialized view", - RelationGetRelationName(rel)))); + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS"))); + + foreach(cell, stmt->relations) + { + Node *rln = (Node *) lfirst(cell); + + if (!IsA(rln, RangeVar)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS"))); + + /* + * CREATE STATISTICS will influence future execution plans but does + * not interfere with currently executing plans. So it should be + * enough to take only ShareUpdateExclusiveLock on relation, + * conflicting with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + */ + rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + + /* Restrict to allowed relation types */ + if (rel->rd_rel->relkind != RELKIND_RELATION && + rel->rd_rel->relkind != RELKIND_MATVIEW && + rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE && + rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("relation \"%s\" is not a table, foreign table, or materialized view", + RelationGetRelationName(rel)))); + + /* You must own the relation to create stats on it */ + if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, + RelationGetRelationName(rel)); + } + + Assert(rel); + relid = RelationGetRelid(rel); /* - * Transform column names to array of attnums. While at it, enforce some - * constraints. + * Currently, we only allow simple column references in the expression + * list. That will change someday, and again the grammar already supports + * it so we have to enforce restrictions here. For now, we can convert + * the expression list to a simple array of attnums. While at it, enforce + * some constraints. */ - foreach(l, stmt->keys) + foreach(cell, stmt->exprs) { - char *attname = strVal(lfirst(l)); + Node *expr = (Node *) lfirst(cell); + ColumnRef *cref; + char *attname; HeapTuple atttuple; Form_pg_attribute attForm; TypeCacheEntry *type; + if (!IsA(expr, ColumnRef)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only simple column references are allowed in CREATE STATISTICS"))); + cref = (ColumnRef *) expr; + + if (list_length(cref->fields) != 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only simple column references are allowed in CREATE STATISTICS"))); + attname = strVal((Value *) linitial(cref->fields)); + atttuple = SearchSysCacheAttName(relid, attname); if (!HeapTupleIsValid(atttuple)) ereport(ERROR, @@ -194,30 +240,29 @@ CreateStatistics(CreateStatsStmt *stmt) stxkeys = buildint2vector(attnums, numcols); /* - * Parse the statistics options. Currently only statistics types are - * recognized. + * Parse the statistics types. */ build_ndistinct = false; build_dependencies = false; - foreach(l, stmt->options) + foreach(cell, stmt->stat_types) { - DefElem *opt = (DefElem *) lfirst(l); + char *type = strVal((Value *) lfirst(cell)); - if (strcmp(opt->defname, "ndistinct") == 0) + if (strcmp(type, "ndistinct") == 0) { - build_ndistinct = defGetBoolean(opt); + build_ndistinct = true; requested_type = true; } - else if (strcmp(opt->defname, "dependencies") == 0) + else if (strcmp(type, "dependencies") == 0) { - build_dependencies = defGetBoolean(opt); + build_dependencies = true; requested_type = true; } else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("unrecognized STATISTICS option \"%s\"", - opt->defname))); + errmsg("unrecognized statistics type \"%s\"", + type))); } /* If no statistic type was specified, build them all. */ if (!requested_type) @@ -268,6 +313,8 @@ CreateStatistics(CreateStatsStmt *stmt) /* * Add a dependency on the table, so that stats get dropped on DROP TABLE. + * + * XXX don't we need dependencies on the specific columns, instead? */ ObjectAddressSet(parentobject, RelationRelationId, relid); ObjectAddressSet(childobject, StatisticExtRelationId, statoid); diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 2d2a9d00b7b..d13a6fc03f0 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3389,9 +3389,9 @@ _copyCreateStatsStmt(const CreateStatsStmt *from) CreateStatsStmt *newnode = makeNode(CreateStatsStmt); COPY_NODE_FIELD(defnames); - COPY_NODE_FIELD(relation); - COPY_NODE_FIELD(keys); - COPY_NODE_FIELD(options); + COPY_NODE_FIELD(stat_types); + COPY_NODE_FIELD(exprs); + COPY_NODE_FIELD(relations); COPY_SCALAR_FIELD(if_not_exists); return newnode; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index b5459cd7260..c9a8c348927 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1349,9 +1349,9 @@ static bool _equalCreateStatsStmt(const CreateStatsStmt *a, const CreateStatsStmt *b) { COMPARE_NODE_FIELD(defnames); - COMPARE_NODE_FIELD(relation); - COMPARE_NODE_FIELD(keys); - COMPARE_NODE_FIELD(options); + COMPARE_NODE_FIELD(stat_types); + COMPARE_NODE_FIELD(exprs); + COMPARE_NODE_FIELD(relations); COMPARE_SCALAR_FIELD(if_not_exists); return true; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 98f67681a7d..3d5b09aeeeb 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2639,9 +2639,9 @@ _outCreateStatsStmt(StringInfo str, const CreateStatsStmt *node) WRITE_NODE_TYPE("CREATESTATSSTMT"); WRITE_NODE_FIELD(defnames); - WRITE_NODE_FIELD(relation); - WRITE_NODE_FIELD(keys); - WRITE_NODE_FIELD(options); + WRITE_NODE_FIELD(stat_types); + WRITE_NODE_FIELD(exprs); + WRITE_NODE_FIELD(relations); WRITE_BOOL_FIELD(if_not_exists); } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index d04bb7ea3eb..28223311e61 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -3835,31 +3835,29 @@ ExistingIndex: USING INDEX index_name { $$ = $3; } /***************************************************************************** * * QUERY : - * CREATE STATISTICS stats_name WITH (options) ON (columns) FROM relname + * CREATE STATISTICS stats_name [(stat types)] + * ON expression-list FROM from_list + * + * Note: the expectation here is that the clauses after ON are a subset of + * SELECT syntax, allowing for expressions and joined tables, and probably + * someday a WHERE clause. Much less than that is currently implemented, + * but the grammar accepts it and then we'll throw FEATURE_NOT_SUPPORTED + * errors as necessary at execution. * *****************************************************************************/ - -CreateStatsStmt: CREATE STATISTICS any_name opt_reloptions ON '(' columnList ')' FROM qualified_name - { - CreateStatsStmt *n = makeNode(CreateStatsStmt); - n->defnames = $3; - n->relation = $10; - n->keys = $7; - n->options = $4; - n->if_not_exists = false; - $$ = (Node *)n; - } - | CREATE STATISTICS IF_P NOT EXISTS any_name opt_reloptions ON '(' columnList ')' FROM qualified_name - { - CreateStatsStmt *n = makeNode(CreateStatsStmt); - n->defnames = $6; - n->relation = $13; - n->keys = $10; - n->options = $7; - n->if_not_exists = true; - $$ = (Node *)n; - } +CreateStatsStmt: + CREATE opt_if_not_exists STATISTICS any_name + opt_name_list ON expr_list FROM from_list + { + CreateStatsStmt *n = makeNode(CreateStatsStmt); + n->defnames = $4; + n->stat_types = $5; + n->exprs = $7; + n->relations = $9; + n->if_not_exists = $2; + $$ = (Node *)n; + } ; /***************************************************************************** diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index cbde1fff01c..983b9800ccd 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -1504,15 +1504,15 @@ pg_get_statisticsext_worker(Oid statextid, bool missing_ok) } /* - * If any option is disabled, then we'll need to append a WITH clause to - * show which options are enabled. We omit the WITH clause on purpose + * If any option is disabled, then we'll need to append the types clause + * to show which options are enabled. We omit the types clause on purpose * when all options are enabled, so a pg_dump/pg_restore will create all * statistics types on a newer postgres version, if the statistics had all * options enabled on the original version. */ if (!ndistinct_enabled || !dependencies_enabled) { - appendStringInfoString(&buf, " WITH ("); + appendStringInfoString(&buf, " ("); if (ndistinct_enabled) appendStringInfoString(&buf, "ndistinct"); else if (dependencies_enabled) @@ -1521,7 +1521,7 @@ pg_get_statisticsext_worker(Oid statextid, bool missing_ok) appendStringInfoChar(&buf, ')'); } - appendStringInfoString(&buf, " ON ("); + appendStringInfoString(&buf, " ON "); for (colno = 0; colno < statextrec->stxkeys.dim1; colno++) { @@ -1536,7 +1536,7 @@ pg_get_statisticsext_worker(Oid statextid, bool missing_ok) appendStringInfoString(&buf, quote_identifier(attname)); } - appendStringInfo(&buf, ") FROM %s", + appendStringInfo(&buf, " FROM %s", generate_relation_name(statextrec->stxrelid, NIL)); ReleaseSysCache(statexttup); diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index b62299929f4..9bd400e067b 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -4955,9 +4955,9 @@ qr/CREATE TRANSFORM FOR integer LANGUAGE sql \(FROM SQL WITH FUNCTION pg_catalog catch_all => 'CREATE ... commands', create_order => 97, create_sql => 'CREATE STATISTICS dump_test.test_ext_stats_no_options - ON (col1, col2) FROM dump_test.test_fifth_table', + ON col1, col2 FROM dump_test.test_fifth_table', regexp => qr/^ - \QCREATE STATISTICS dump_test.test_ext_stats_no_options ON (col1, col2) FROM test_fifth_table;\E + \QCREATE STATISTICS dump_test.test_ext_stats_no_options ON col1, col2 FROM test_fifth_table;\E /xms, like => { binary_upgrade => 1, @@ -4988,10 +4988,10 @@ qr/CREATE TRANSFORM FOR integer LANGUAGE sql \(FROM SQL WITH FUNCTION pg_catalog all_runs => 1, catch_all => 'CREATE ... commands', create_order => 97, - create_sql => 'CREATE STATISTICS dump_test.test_ext_stats_using - WITH (ndistinct) ON (col1, col2) FROM dump_test.test_fifth_table', + create_sql => 'CREATE STATISTICS dump_test.test_ext_stats_opts + (ndistinct) ON col1, col2 FROM dump_test.test_fifth_table', regexp => qr/^ - \QCREATE STATISTICS dump_test.test_ext_stats_using WITH (ndistinct) ON (col1, col2) FROM test_fifth_table;\E + \QCREATE STATISTICS dump_test.test_ext_stats_opts (ndistinct) ON col1, col2 FROM test_fifth_table;\E /xms, like => { binary_upgrade => 1, diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 13395f5ca67..386af6168fe 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -2355,8 +2355,9 @@ describeOneTableDetails(const char *schemaname, { printfPQExpBuffer(&buf, "SELECT oid, " + "stxrelid::pg_catalog.regclass, " "stxnamespace::pg_catalog.regnamespace AS nsp, " - "stxname, stxkeys,\n" + "stxname,\n" " (SELECT pg_catalog.string_agg(pg_catalog.quote_ident(attname),', ')\n" " FROM pg_catalog.unnest(stxkeys) s(attnum)\n" " JOIN pg_catalog.pg_attribute a ON (stxrelid = a.attrelid AND\n" @@ -2385,9 +2386,9 @@ describeOneTableDetails(const char *schemaname, printfPQExpBuffer(&buf, " "); /* statistics name (qualified with namespace) */ - appendPQExpBuffer(&buf, "\"%s.%s\" WITH (", - PQgetvalue(result, i, 1), - PQgetvalue(result, i, 2)); + appendPQExpBuffer(&buf, "\"%s\".\"%s\" (", + PQgetvalue(result, i, 2), + PQgetvalue(result, i, 3)); /* options */ if (strcmp(PQgetvalue(result, i, 5), "t") == 0) @@ -2401,8 +2402,9 @@ describeOneTableDetails(const char *schemaname, appendPQExpBuffer(&buf, "%sdependencies", gotone ? ", " : ""); } - appendPQExpBuffer(&buf, ") ON (%s)", - PQgetvalue(result, i, 4)); + appendPQExpBuffer(&buf, ") ON %s FROM %s", + PQgetvalue(result, i, 4), + PQgetvalue(result, i, 1)); printTableAddFooter(&cont, buf.data); } diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 92abcc3ac38..09fb30f270c 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -616,6 +616,21 @@ static const SchemaQuery Query_for_list_of_matviews = { NULL }; +static const SchemaQuery Query_for_list_of_statistics = { + /* catname */ + "pg_catalog.pg_statistic_ext s", + /* selcondition */ + NULL, + /* viscondition */ + NULL, + /* namespace */ + "s.stxnamespace", + /* result */ + "pg_catalog.quote_ident(s.stxname)", + /* qualresult */ + NULL +}; + /* * Queries to get lists of names of various kinds of things, possibly @@ -1023,6 +1038,7 @@ static const pgsql_thing_t words_after_create[] = { {"SCHEMA", Query_for_list_of_schemas}, {"SEQUENCE", NULL, &Query_for_list_of_sequences}, {"SERVER", Query_for_list_of_servers}, + {"STATISTICS", NULL, &Query_for_list_of_statistics}, {"SUBSCRIPTION", Query_for_list_of_subscriptions}, {"SYSTEM", NULL, NULL, THING_NO_CREATE | THING_NO_DROP}, {"TABLE", NULL, &Query_for_list_of_tables}, @@ -1782,6 +1798,10 @@ psql_completion(const char *text, int start, int end) else if (Matches5("ALTER", "RULE", MatchAny, "ON", MatchAny)) COMPLETE_WITH_CONST("RENAME TO"); + /* ALTER STATISTICS <name> */ + else if (Matches3("ALTER", "STATISTICS", MatchAny)) + COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA"); + /* ALTER TRIGGER <name>, add ON */ else if (Matches3("ALTER", "TRIGGER", MatchAny)) COMPLETE_WITH_CONST("ON"); @@ -2118,7 +2138,8 @@ psql_completion(const char *text, int start, int end) {"ACCESS METHOD", "CAST", "COLLATION", "CONVERSION", "DATABASE", "EVENT TRIGGER", "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", - "SERVER", "INDEX", "LANGUAGE", "POLICY", "PUBLICATION", "RULE", "SCHEMA", "SEQUENCE", "SUBSCRIPTION", + "SERVER", "INDEX", "LANGUAGE", "POLICY", "PUBLICATION", "RULE", + "SCHEMA", "SEQUENCE", "STATISTICS", "SUBSCRIPTION", "TABLE", "TYPE", "VIEW", "MATERIALIZED VIEW", "COLUMN", "AGGREGATE", "FUNCTION", "OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN", "LARGE OBJECT", "TABLESPACE", "TEXT SEARCH", "ROLE", NULL}; @@ -2380,6 +2401,19 @@ psql_completion(const char *text, int start, int end) else if (Matches3("CREATE", "SERVER", MatchAny)) COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER"); +/* CREATE STATISTICS <name> */ + else if (Matches3("CREATE", "STATISTICS", MatchAny)) + COMPLETE_WITH_LIST2("(", "ON"); + else if (Matches4("CREATE", "STATISTICS", MatchAny, "(")) + COMPLETE_WITH_LIST2("ndistinct", "dependencies"); + else if (HeadMatches3("CREATE", "STATISTICS", MatchAny) && + previous_words[0][0] == '(' && + previous_words[0][strlen(previous_words[0]) - 1] == ')') + COMPLETE_WITH_CONST("ON"); + else if (HeadMatches3("CREATE", "STATISTICS", MatchAny) && + TailMatches1("FROM")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + /* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */ /* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */ else if (TailMatches2("CREATE", "TEMP|TEMPORARY")) @@ -2585,7 +2619,7 @@ psql_completion(const char *text, int start, int end) /* DROP */ /* Complete DROP object with CASCADE / RESTRICT */ else if (Matches3("DROP", - "COLLATION|CONVERSION|DOMAIN|EXTENSION|LANGUAGE|PUBLICATION|SCHEMA|SEQUENCE|SERVER|SUBSCRIPTION|TABLE|TYPE|VIEW", + "COLLATION|CONVERSION|DOMAIN|EXTENSION|LANGUAGE|PUBLICATION|SCHEMA|SEQUENCE|SERVER|SUBSCRIPTION|STATISTICS|TABLE|TYPE|VIEW", MatchAny) || Matches4("DROP", "ACCESS", "METHOD", MatchAny) || (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, MatchAny) && diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 46c23c25306..d396be382b5 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2689,10 +2689,10 @@ typedef struct CreateStatsStmt { NodeTag type; List *defnames; /* qualified name (list of Value strings) */ - RangeVar *relation; /* relation to build statistics on */ - List *keys; /* String nodes naming referenced columns */ - List *options; /* list of DefElem */ - bool if_not_exists; /* do nothing if statistics already exists */ + List *stat_types; /* stat types (list of Value strings) */ + List *exprs; /* expressions to build statistics on */ + List *relations; /* rels to build stats on (list of RangeVar) */ + bool if_not_exists; /* do nothing if stats name already exists */ } CreateStatsStmt; /* ---------------------- diff --git a/src/test/regress/expected/alter_generic.out b/src/test/regress/expected/alter_generic.out index a81a4edfb2b..28e69166beb 100644 --- a/src/test/regress/expected/alter_generic.out +++ b/src/test/regress/expected/alter_generic.out @@ -501,8 +501,8 @@ DROP OPERATOR FAMILY alt_opf18 USING btree; -- SET SESSION AUTHORIZATION regress_alter_user1; CREATE TABLE alt_regress_1 (a INTEGER, b INTEGER); -CREATE STATISTICS alt_stat1 ON (a, b) FROM alt_regress_1; -CREATE STATISTICS alt_stat2 ON (a, b) FROM alt_regress_1; +CREATE STATISTICS alt_stat1 ON a, b FROM alt_regress_1; +CREATE STATISTICS alt_stat2 ON a, b FROM alt_regress_1; ALTER STATISTICS alt_stat1 RENAME TO alt_stat2; -- failed (name conflict) ERROR: statistics "alt_stat2" already exists in schema "alt_nsp1" ALTER STATISTICS alt_stat1 RENAME TO alt_stat3; -- failed (name conflict) @@ -511,8 +511,9 @@ ERROR: must be member of role "regress_alter_user2" ALTER STATISTICS alt_stat2 OWNER TO regress_alter_user3; -- OK ALTER STATISTICS alt_stat2 SET SCHEMA alt_nsp2; -- OK SET SESSION AUTHORIZATION regress_alter_user2; -CREATE STATISTICS alt_stat1 ON (a, b) FROM alt_regress_1; -CREATE STATISTICS alt_stat2 ON (a, b) FROM alt_regress_1; +CREATE TABLE alt_regress_2 (a INTEGER, b INTEGER); +CREATE STATISTICS alt_stat1 ON a, b FROM alt_regress_2; +CREATE STATISTICS alt_stat2 ON a, b FROM alt_regress_2; ALTER STATISTICS alt_stat3 RENAME TO alt_stat4; -- failed (not owner) ERROR: must be owner of statistics alt_stat3 ALTER STATISTICS alt_stat1 RENAME TO alt_stat4; -- OK @@ -672,54 +673,13 @@ SELECT nspname, prsname --- --- Cleanup resources --- +set client_min_messages to warning; -- suppress cascade notices DROP FOREIGN DATA WRAPPER alt_fdw2 CASCADE; -NOTICE: drop cascades to server alt_fserv2 DROP FOREIGN DATA WRAPPER alt_fdw3 CASCADE; -NOTICE: drop cascades to server alt_fserv3 DROP LANGUAGE alt_lang2 CASCADE; DROP LANGUAGE alt_lang3 CASCADE; -DROP LANGUAGE alt_lang4 CASCADE; -ERROR: language "alt_lang4" does not exist DROP SCHEMA alt_nsp1 CASCADE; -NOTICE: drop cascades to 27 other objects -DETAIL: drop cascades to function alt_func3(integer) -drop cascades to function alt_agg3(integer) -drop cascades to function alt_func4(integer) -drop cascades to function alt_func2(integer) -drop cascades to function alt_agg4(integer) -drop cascades to function alt_agg2(integer) -drop cascades to conversion alt_conv3 -drop cascades to conversion alt_conv4 -drop cascades to conversion alt_conv2 -drop cascades to operator @+@(integer,integer) -drop cascades to operator @-@(integer,integer) -drop cascades to operator family alt_opf3 for access method hash -drop cascades to operator family alt_opc1 for access method hash -drop cascades to operator family alt_opc2 for access method hash -drop cascades to operator family alt_opf4 for access method hash -drop cascades to operator family alt_opf2 for access method hash -drop cascades to table alt_regress_1 -drop cascades to text search dictionary alt_ts_dict3 -drop cascades to text search dictionary alt_ts_dict4 -drop cascades to text search dictionary alt_ts_dict2 -drop cascades to text search configuration alt_ts_conf3 -drop cascades to text search configuration alt_ts_conf4 -drop cascades to text search configuration alt_ts_conf2 -drop cascades to text search template alt_ts_temp3 -drop cascades to text search template alt_ts_temp2 -drop cascades to text search parser alt_ts_prs3 -drop cascades to text search parser alt_ts_prs2 DROP SCHEMA alt_nsp2 CASCADE; -NOTICE: drop cascades to 9 other objects -DETAIL: drop cascades to function alt_nsp2.alt_func2(integer) -drop cascades to function alt_nsp2.alt_agg2(integer) -drop cascades to conversion alt_conv2 -drop cascades to operator alt_nsp2.@-@(integer,integer) -drop cascades to operator family alt_nsp2.alt_opf2 for access method hash -drop cascades to text search dictionary alt_ts_dict2 -drop cascades to text search configuration alt_ts_conf2 -drop cascades to text search template alt_ts_temp2 -drop cascades to text search parser alt_ts_prs2 DROP USER regress_alter_user1; DROP USER regress_alter_user2; DROP USER regress_alter_user3; diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out index 700f2618271..3ca5c764ca6 100644 --- a/src/test/regress/expected/object_address.out +++ b/src/test/regress/expected/object_address.out @@ -39,7 +39,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL ( CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable; CREATE SUBSCRIPTION addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE); WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables -CREATE STATISTICS addr_nsp.gentable_stat ON (a,b) FROM addr_nsp.gentable; +CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable; -- test some error cases SELECT pg_get_object_address('stone', '{}', '{}'); ERROR: unrecognized object type "stone" diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out index 92ac84ac671..4ccdf21a015 100644 --- a/src/test/regress/expected/stats_ext.out +++ b/src/test/regress/expected/stats_ext.out @@ -5,24 +5,49 @@ SET max_parallel_workers = 0; SET max_parallel_workers_per_gather = 0; SET work_mem = '128kB'; +-- Verify failures +CREATE STATISTICS tst; +ERROR: syntax error at or near ";" +LINE 1: CREATE STATISTICS tst; + ^ +CREATE STATISTICS tst ON a, b; +ERROR: syntax error at or near ";" +LINE 1: CREATE STATISTICS tst ON a, b; + ^ +CREATE STATISTICS tst FROM sometab; +ERROR: syntax error at or near "FROM" +LINE 1: CREATE STATISTICS tst FROM sometab; + ^ +CREATE STATISTICS tst ON a, b FROM nonexistant; +ERROR: relation "nonexistant" does not exist +CREATE STATISTICS tst ON a, b FROM pg_class; +ERROR: column "a" referenced in statistics does not exist +CREATE STATISTICS tst ON relname, relname, relnatts FROM pg_class; +ERROR: duplicate column name in statistics definition +CREATE STATISTICS tst ON relnatts + relpages FROM pg_class; +ERROR: only simple column references are allowed in CREATE STATISTICS +CREATE STATISTICS tst ON (relpages, reltuples) FROM pg_class; +ERROR: only simple column references are allowed in CREATE STATISTICS +CREATE STATISTICS tst (unrecognized) ON relname, relnatts FROM pg_class; +ERROR: unrecognized statistics type "unrecognized" -- Ensure stats are dropped sanely CREATE TABLE ab1 (a INTEGER, b INTEGER, c INTEGER); -CREATE STATISTICS ab1_a_b_stats ON (a, b) FROM ab1; +CREATE STATISTICS ab1_a_b_stats ON a, b FROM ab1; DROP STATISTICS ab1_a_b_stats; CREATE SCHEMA regress_schema_2; -CREATE STATISTICS regress_schema_2.ab1_a_b_stats ON (a, b) FROM ab1; +CREATE STATISTICS regress_schema_2.ab1_a_b_stats ON a, b FROM ab1; -- Let's also verify the pg_get_statisticsextdef output looks sane. SELECT pg_get_statisticsextdef(oid) FROM pg_statistic_ext WHERE stxname = 'ab1_a_b_stats'; - pg_get_statisticsextdef ---------------------------------------------------------------------- - CREATE STATISTICS regress_schema_2.ab1_a_b_stats ON (a, b) FROM ab1 + pg_get_statisticsextdef +------------------------------------------------------------------- + CREATE STATISTICS regress_schema_2.ab1_a_b_stats ON a, b FROM ab1 (1 row) DROP STATISTICS regress_schema_2.ab1_a_b_stats; -- Ensure statistics are dropped when columns are -CREATE STATISTICS ab1_b_c_stats ON (b, c) FROM ab1; -CREATE STATISTICS ab1_a_b_c_stats ON (a, b, c) FROM ab1; -CREATE STATISTICS ab1_a_b_stats ON (a, b) FROM ab1; +CREATE STATISTICS ab1_b_c_stats ON b, c FROM ab1; +CREATE STATISTICS ab1_a_b_c_stats ON a, b, c FROM ab1; +CREATE STATISTICS ab1_a_b_stats ON a, b FROM ab1; ALTER TABLE ab1 DROP COLUMN a; \d ab1 Table "public.ab1" @@ -31,14 +56,14 @@ ALTER TABLE ab1 DROP COLUMN a; b | integer | | | c | integer | | | Statistics: - "public.ab1_b_c_stats" WITH (ndistinct, dependencies) ON (b, c) + "public"."ab1_b_c_stats" (ndistinct, dependencies) ON b, c FROM ab1 DROP TABLE ab1; -- Ensure things work sanely with SET STATISTICS 0 CREATE TABLE ab1 (a INTEGER, b INTEGER); ALTER TABLE ab1 ALTER a SET STATISTICS 0; INSERT INTO ab1 SELECT a, a%23 FROM generate_series(1, 1000) a; -CREATE STATISTICS ab1_a_b_stats ON (a, b) FROM ab1; +CREATE STATISTICS ab1_a_b_stats ON a, b FROM ab1; ANALYZE ab1; WARNING: extended statistics "public.ab1_a_b_stats" could not be collected for relation public.ab1 ALTER TABLE ab1 ALTER a SET STATISTICS -1; @@ -60,24 +85,24 @@ CREATE SERVER extstats_dummy_srv FOREIGN DATA WRAPPER extstats_dummy_fdw; CREATE FOREIGN TABLE tststats.f (a int, b int, c text) SERVER extstats_dummy_srv; CREATE TABLE tststats.pt (a int, b int, c text) PARTITION BY RANGE (a, b); CREATE TABLE tststats.pt1 PARTITION OF tststats.pt FOR VALUES FROM (-10, -10) TO (10, 10); -CREATE STATISTICS tststats.s1 ON (a, b) FROM tststats.t; -CREATE STATISTICS tststats.s2 ON (a, b) FROM tststats.ti; +CREATE STATISTICS tststats.s1 ON a, b FROM tststats.t; +CREATE STATISTICS tststats.s2 ON a, b FROM tststats.ti; ERROR: relation "ti" is not a table, foreign table, or materialized view -CREATE STATISTICS tststats.s3 ON (a, b) FROM tststats.s; +CREATE STATISTICS tststats.s3 ON a, b FROM tststats.s; ERROR: relation "s" is not a table, foreign table, or materialized view -CREATE STATISTICS tststats.s4 ON (a, b) FROM tststats.v; +CREATE STATISTICS tststats.s4 ON a, b FROM tststats.v; ERROR: relation "v" is not a table, foreign table, or materialized view -CREATE STATISTICS tststats.s5 ON (a, b) FROM tststats.mv; -CREATE STATISTICS tststats.s6 ON (a, b) FROM tststats.ty; +CREATE STATISTICS tststats.s5 ON a, b FROM tststats.mv; +CREATE STATISTICS tststats.s6 ON a, b FROM tststats.ty; ERROR: relation "ty" is not a table, foreign table, or materialized view -CREATE STATISTICS tststats.s7 ON (a, b) FROM tststats.f; -CREATE STATISTICS tststats.s8 ON (a, b) FROM tststats.pt; -CREATE STATISTICS tststats.s9 ON (a, b) FROM tststats.pt1; +CREATE STATISTICS tststats.s7 ON a, b FROM tststats.f; +CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt; +CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1; DO $$ DECLARE relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass; BEGIN - EXECUTE 'CREATE STATISTICS tststats.s10 ON (a, b) FROM ' || relname; + EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname; EXCEPTION WHEN wrong_object_type THEN RAISE NOTICE 'stats on toast table not created'; END; @@ -158,20 +183,8 @@ EXPLAIN (COSTS off) -> Seq Scan on ndistinct (5 rows) --- unknown column -CREATE STATISTICS s10 ON (unknown_column) FROM ndistinct; -ERROR: column "unknown_column" referenced in statistics does not exist --- single column -CREATE STATISTICS s10 ON (a) FROM ndistinct; -ERROR: extended statistics require at least 2 columns --- single column, duplicated -CREATE STATISTICS s10 ON (a,a) FROM ndistinct; -ERROR: duplicate column name in statistics definition --- two columns, one duplicated -CREATE STATISTICS s10 ON (a, a, b) FROM ndistinct; -ERROR: duplicate column name in statistics definition -- correct command -CREATE STATISTICS s10 ON (a, b, c) FROM ndistinct; +CREATE STATISTICS s10 ON a, b, c FROM ndistinct; ANALYZE ndistinct; SELECT stxkind, stxndistinct FROM pg_statistic_ext WHERE stxrelid = 'ndistinct'::regclass; @@ -352,7 +365,6 @@ EXPLAIN (COSTS off) -> Seq Scan on ndistinct (3 rows) -DROP TABLE ndistinct; -- functional dependencies tests CREATE TABLE functional_dependencies ( filler1 TEXT, @@ -389,7 +401,7 @@ EXPLAIN (COSTS OFF) (2 rows) -- create statistics -CREATE STATISTICS func_deps_stat WITH (dependencies) ON (a, b, c) FROM functional_dependencies; +CREATE STATISTICS func_deps_stat (dependencies) ON a, b, c FROM functional_dependencies; ANALYZE functional_dependencies; EXPLAIN (COSTS OFF) SELECT * FROM functional_dependencies WHERE a = 1 AND b = '1'; @@ -432,7 +444,7 @@ EXPLAIN (COSTS OFF) (2 rows) -- create statistics -CREATE STATISTICS func_deps_stat WITH (dependencies) ON (a, b, c) FROM functional_dependencies; +CREATE STATISTICS func_deps_stat (dependencies) ON a, b, c FROM functional_dependencies; ANALYZE functional_dependencies; EXPLAIN (COSTS OFF) SELECT * FROM functional_dependencies WHERE a = 1 AND b = '1'; @@ -456,4 +468,3 @@ EXPLAIN (COSTS OFF) (5 rows) RESET random_page_cost; -DROP TABLE functional_dependencies; diff --git a/src/test/regress/sql/alter_generic.sql b/src/test/regress/sql/alter_generic.sql index 88e8d7eb86b..342f82856e1 100644 --- a/src/test/regress/sql/alter_generic.sql +++ b/src/test/regress/sql/alter_generic.sql @@ -438,8 +438,8 @@ DROP OPERATOR FAMILY alt_opf18 USING btree; -- SET SESSION AUTHORIZATION regress_alter_user1; CREATE TABLE alt_regress_1 (a INTEGER, b INTEGER); -CREATE STATISTICS alt_stat1 ON (a, b) FROM alt_regress_1; -CREATE STATISTICS alt_stat2 ON (a, b) FROM alt_regress_1; +CREATE STATISTICS alt_stat1 ON a, b FROM alt_regress_1; +CREATE STATISTICS alt_stat2 ON a, b FROM alt_regress_1; ALTER STATISTICS alt_stat1 RENAME TO alt_stat2; -- failed (name conflict) ALTER STATISTICS alt_stat1 RENAME TO alt_stat3; -- failed (name conflict) @@ -448,8 +448,9 @@ ALTER STATISTICS alt_stat2 OWNER TO regress_alter_user3; -- OK ALTER STATISTICS alt_stat2 SET SCHEMA alt_nsp2; -- OK SET SESSION AUTHORIZATION regress_alter_user2; -CREATE STATISTICS alt_stat1 ON (a, b) FROM alt_regress_1; -CREATE STATISTICS alt_stat2 ON (a, b) FROM alt_regress_1; +CREATE TABLE alt_regress_2 (a INTEGER, b INTEGER); +CREATE STATISTICS alt_stat1 ON a, b FROM alt_regress_2; +CREATE STATISTICS alt_stat2 ON a, b FROM alt_regress_2; ALTER STATISTICS alt_stat3 RENAME TO alt_stat4; -- failed (not owner) ALTER STATISTICS alt_stat1 RENAME TO alt_stat4; -- OK @@ -572,12 +573,13 @@ SELECT nspname, prsname --- --- Cleanup resources --- +set client_min_messages to warning; -- suppress cascade notices + DROP FOREIGN DATA WRAPPER alt_fdw2 CASCADE; DROP FOREIGN DATA WRAPPER alt_fdw3 CASCADE; DROP LANGUAGE alt_lang2 CASCADE; DROP LANGUAGE alt_lang3 CASCADE; -DROP LANGUAGE alt_lang4 CASCADE; DROP SCHEMA alt_nsp1 CASCADE; DROP SCHEMA alt_nsp2 CASCADE; diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql index 8a738e20d68..f25ed735e1b 100644 --- a/src/test/regress/sql/object_address.sql +++ b/src/test/regress/sql/object_address.sql @@ -41,7 +41,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL ( TO SQL WITH FUNCTION int4recv(internal)); CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable; CREATE SUBSCRIPTION addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE); -CREATE STATISTICS addr_nsp.gentable_stat ON (a,b) FROM addr_nsp.gentable; +CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable; -- test some error cases SELECT pg_get_object_address('stone', '{}', '{}'); diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql index 72c7659c4b1..4050f33c088 100644 --- a/src/test/regress/sql/stats_ext.sql +++ b/src/test/regress/sql/stats_ext.sql @@ -7,13 +7,24 @@ SET max_parallel_workers = 0; SET max_parallel_workers_per_gather = 0; SET work_mem = '128kB'; +-- Verify failures +CREATE STATISTICS tst; +CREATE STATISTICS tst ON a, b; +CREATE STATISTICS tst FROM sometab; +CREATE STATISTICS tst ON a, b FROM nonexistant; +CREATE STATISTICS tst ON a, b FROM pg_class; +CREATE STATISTICS tst ON relname, relname, relnatts FROM pg_class; +CREATE STATISTICS tst ON relnatts + relpages FROM pg_class; +CREATE STATISTICS tst ON (relpages, reltuples) FROM pg_class; +CREATE STATISTICS tst (unrecognized) ON relname, relnatts FROM pg_class; + -- Ensure stats are dropped sanely CREATE TABLE ab1 (a INTEGER, b INTEGER, c INTEGER); -CREATE STATISTICS ab1_a_b_stats ON (a, b) FROM ab1; +CREATE STATISTICS ab1_a_b_stats ON a, b FROM ab1; DROP STATISTICS ab1_a_b_stats; CREATE SCHEMA regress_schema_2; -CREATE STATISTICS regress_schema_2.ab1_a_b_stats ON (a, b) FROM ab1; +CREATE STATISTICS regress_schema_2.ab1_a_b_stats ON a, b FROM ab1; -- Let's also verify the pg_get_statisticsextdef output looks sane. SELECT pg_get_statisticsextdef(oid) FROM pg_statistic_ext WHERE stxname = 'ab1_a_b_stats'; @@ -21,9 +32,9 @@ SELECT pg_get_statisticsextdef(oid) FROM pg_statistic_ext WHERE stxname = 'ab1_a DROP STATISTICS regress_schema_2.ab1_a_b_stats; -- Ensure statistics are dropped when columns are -CREATE STATISTICS ab1_b_c_stats ON (b, c) FROM ab1; -CREATE STATISTICS ab1_a_b_c_stats ON (a, b, c) FROM ab1; -CREATE STATISTICS ab1_a_b_stats ON (a, b) FROM ab1; +CREATE STATISTICS ab1_b_c_stats ON b, c FROM ab1; +CREATE STATISTICS ab1_a_b_c_stats ON a, b, c FROM ab1; +CREATE STATISTICS ab1_a_b_stats ON a, b FROM ab1; ALTER TABLE ab1 DROP COLUMN a; \d ab1 DROP TABLE ab1; @@ -32,7 +43,7 @@ DROP TABLE ab1; CREATE TABLE ab1 (a INTEGER, b INTEGER); ALTER TABLE ab1 ALTER a SET STATISTICS 0; INSERT INTO ab1 SELECT a, a%23 FROM generate_series(1, 1000) a; -CREATE STATISTICS ab1_a_b_stats ON (a, b) FROM ab1; +CREATE STATISTICS ab1_a_b_stats ON a, b FROM ab1; ANALYZE ab1; ALTER TABLE ab1 ALTER a SET STATISTICS -1; -- partial analyze doesn't build stats either @@ -55,20 +66,20 @@ CREATE FOREIGN TABLE tststats.f (a int, b int, c text) SERVER extstats_dummy_srv CREATE TABLE tststats.pt (a int, b int, c text) PARTITION BY RANGE (a, b); CREATE TABLE tststats.pt1 PARTITION OF tststats.pt FOR VALUES FROM (-10, -10) TO (10, 10); -CREATE STATISTICS tststats.s1 ON (a, b) FROM tststats.t; -CREATE STATISTICS tststats.s2 ON (a, b) FROM tststats.ti; -CREATE STATISTICS tststats.s3 ON (a, b) FROM tststats.s; -CREATE STATISTICS tststats.s4 ON (a, b) FROM tststats.v; -CREATE STATISTICS tststats.s5 ON (a, b) FROM tststats.mv; -CREATE STATISTICS tststats.s6 ON (a, b) FROM tststats.ty; -CREATE STATISTICS tststats.s7 ON (a, b) FROM tststats.f; -CREATE STATISTICS tststats.s8 ON (a, b) FROM tststats.pt; -CREATE STATISTICS tststats.s9 ON (a, b) FROM tststats.pt1; +CREATE STATISTICS tststats.s1 ON a, b FROM tststats.t; +CREATE STATISTICS tststats.s2 ON a, b FROM tststats.ti; +CREATE STATISTICS tststats.s3 ON a, b FROM tststats.s; +CREATE STATISTICS tststats.s4 ON a, b FROM tststats.v; +CREATE STATISTICS tststats.s5 ON a, b FROM tststats.mv; +CREATE STATISTICS tststats.s6 ON a, b FROM tststats.ty; +CREATE STATISTICS tststats.s7 ON a, b FROM tststats.f; +CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt; +CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1; DO $$ DECLARE relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass; BEGIN - EXECUTE 'CREATE STATISTICS tststats.s10 ON (a, b) FROM ' || relname; + EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname; EXCEPTION WHEN wrong_object_type THEN RAISE NOTICE 'stats on toast table not created'; END; @@ -113,20 +124,8 @@ EXPLAIN (COSTS off) EXPLAIN (COSTS off) SELECT COUNT(*) FROM ndistinct GROUP BY b, c, d; --- unknown column -CREATE STATISTICS s10 ON (unknown_column) FROM ndistinct; - --- single column -CREATE STATISTICS s10 ON (a) FROM ndistinct; - --- single column, duplicated -CREATE STATISTICS s10 ON (a,a) FROM ndistinct; - --- two columns, one duplicated -CREATE STATISTICS s10 ON (a, a, b) FROM ndistinct; - -- correct command -CREATE STATISTICS s10 ON (a, b, c) FROM ndistinct; +CREATE STATISTICS s10 ON a, b, c FROM ndistinct; ANALYZE ndistinct; @@ -202,8 +201,6 @@ EXPLAIN (COSTS off) EXPLAIN (COSTS off) SELECT COUNT(*) FROM ndistinct GROUP BY a, d; -DROP TABLE ndistinct; - -- functional dependencies tests CREATE TABLE functional_dependencies ( filler1 TEXT, @@ -233,7 +230,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM functional_dependencies WHERE a = 1 AND b = '1' AND c = 1; -- create statistics -CREATE STATISTICS func_deps_stat WITH (dependencies) ON (a, b, c) FROM functional_dependencies; +CREATE STATISTICS func_deps_stat (dependencies) ON a, b, c FROM functional_dependencies; ANALYZE functional_dependencies; @@ -259,7 +256,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM functional_dependencies WHERE a = 1 AND b = '1' AND c = 1; -- create statistics -CREATE STATISTICS func_deps_stat WITH (dependencies) ON (a, b, c) FROM functional_dependencies; +CREATE STATISTICS func_deps_stat (dependencies) ON a, b, c FROM functional_dependencies; ANALYZE functional_dependencies; @@ -270,4 +267,3 @@ EXPLAIN (COSTS OFF) SELECT * FROM functional_dependencies WHERE a = 1 AND b = '1' AND c = 1; RESET random_page_cost; -DROP TABLE functional_dependencies; |