Tom Lane wrote:
> Alvaro Herrera <alvhe...@2ndquadrant.com> writes:
> > Tom Lane wrote:
> >> Have you thought further about the upthread suggestion to just borrow
> >> SELECT's syntax lock stock and barrel?
> 
> > Bison seems to like the productions below.  Is this what you had in
> > mind?  These mostly mimic joined_table and table_ref, stripping out the
> > rules that we don't need.
> 
> I'd suggest just using the from_list production and then complaining
> at runtime if what you get is too complicated.  Otherwise, you have
> to maintain a duplicate set of productions, and you're going to be
> unable to throw anything more informative than "syntax error" when
> somebody tries to exceed the implementation limits.

Hmm, yeah, makes sense.  Here's a patch for this approach.  I ended up
using on ON again for the list of columns.  I suppose the checks in
CreateStatistics() could still be improved, but I'd go ahead and push
this tomorrow morning and we can hammer those details later on, if it's
still needed.  Better avoid shipping beta with outdated grammar ...

BTW the new castNode() family of macros don't work with Value nodes
(because the tags are different depending on what's stored, but each
type does not have its own struct.  Oh well.)

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
diff --git a/doc/src/sgml/perform.sgml b/doc/src/sgml/perform.sgml
index b10b734b90..32e17ee5f8 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 11580bfd22..ef847b9633 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/create_statistics.sgml 
b/doc/src/sgml/ref/create_statistics.sgml
index edbcf5840b..84ff52df04 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>
 
@@ -75,6 +75,19 @@ CREATE STATISTICS [ IF NOT EXISTS ] <replaceable 
class="PARAMETER">statistics_na
    </varlistentry>
 
    <varlistentry>
+    <term><replaceable class="PARAMETER">statistic_type</replaceable></term>
+    <listitem>
+     <para>
+      A statistic type to be enabled for this statistics.  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">column_name</replaceable></term>
     <listitem>
      <para>
@@ -94,42 +107,6 @@ CREATE STATISTICS [ IF NOT EXISTS ] <replaceable 
class="PARAMETER">statistics_na
    </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>
-    <listitem>
-     <para>
-      Enables functional dependencies for the statistics.
-     </para>
-    </listitem>
-   </varlistentry>
-
-   <varlistentry>
-    <term><literal>ndistinct</> (<type>boolean</>)</term>
-    <listitem>
-     <para>
-      Enables ndistinct coefficients for the statistics.
-     </para>
-    </listitem>
-   </varlistentry>
-
-   </variablelist>
-
-  </refsect2>
  </refsect1>
 
  <refsect1>
@@ -158,7 +135,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;
 
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 0b9c33e30a..220a3e92a2 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -63,7 +63,7 @@ CreateStatistics(CreateStatsStmt *stmt)
        Relation        rel;
        Oid                     relid;
        ObjectAddress parentobject,
-                               childobject;
+                                 childobject;
        Datum           types[2];               /* one for each possible type 
of statistics */
        int                     ntypes;
        ArrayType  *stxkind;
@@ -71,7 +71,7 @@ CreateStatistics(CreateStatsStmt *stmt)
        bool            build_dependencies;
        bool            requested_type = false;
        int                     i;
-       ListCell   *l;
+       ListCell   *cell;
 
        Assert(IsA(stmt, CreateStatsStmt));
 
@@ -100,42 +100,78 @@ CreateStatistics(CreateStatsStmt *stmt)
                                 errmsg("statistics \"%s\" already exist", 
namestr)));
        }
 
-       /*
-        * 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(stmt->relation, ShareUpdateExclusiveLock);
-       relid = RelationGetRelid(rel);
+       relid = InvalidOid;
 
-       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("cross-table statistics not supported 
yet")));
+
+       foreach(cell, stmt->relations)
+       {
+               Node       *rln = 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(castNode(RangeVar, rln), 
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)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                        errmsg("relation \"%s\" is not a 
table, foreign table, or materialized view",
+                                                       
RelationGetRelationName(rel))));
+       }
+       if (relid == InvalidOid)
+               elog(ERROR, "could not find OID for relation in statistic 
object");
 
        /*
         * Transform column names to 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_SYNTAX_ERROR),
+                                        errmsg("only simple column references 
are allowed in %s",
+                                                       "CREATE STATISTICS")));
+
+               cref = castNode(ColumnRef, expr);
+               if (list_length(cref->fields) > 1)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("only simple column references 
are allowed in %s",
+                                                       "CREATE STATISTICS")));
+
+               attname = strVal((Value *) linitial(cref->fields));
+
                atttuple = SearchSysCacheAttName(relid, attname);
                if (!HeapTupleIsValid(atttuple))
                        ereport(ERROR,
                                        (errcode(ERRCODE_UNDEFINED_COLUMN),
-                         errmsg("column \"%s\" referenced in statistics does 
not exist",
-                                        attname)));
+                                        errmsg("column \"%s\" referenced in 
statistics does not exist",
+                                                       attname)));
                attForm = (Form_pg_attribute) GETSTRUCT(atttuple);
 
                /* Disallow use of system attributes in extended stats */
@@ -188,7 +224,7 @@ CreateStatistics(CreateStatsStmt *stmt)
                if (attnums[i] == attnums[i - 1])
                        ereport(ERROR,
                                        (errcode(ERRCODE_DUPLICATE_COLUMN),
-                                 errmsg("duplicate column name in statistics 
definition")));
+                                        errmsg("duplicate column name in 
statistics definition")));
 
        /* Form an int2vector representation of the sorted column list */
        stxkeys = buildint2vector(attnums, numcols);
@@ -199,25 +235,25 @@ CreateStatistics(CreateStatsStmt *stmt)
         */
        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)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2d2a9d00b7..edad16cdb9 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3389,14 +3389,25 @@ _copyCreateStatsStmt(const CreateStatsStmt *from)
        CreateStatsStmt *newnode = makeNode(CreateStatsStmt);
 
        COPY_NODE_FIELD(defnames);
-       COPY_NODE_FIELD(relation);
-       COPY_NODE_FIELD(keys);
+       COPY_NODE_FIELD(relations);
+       COPY_NODE_FIELD(exprs);
        COPY_NODE_FIELD(options);
        COPY_SCALAR_FIELD(if_not_exists);
 
        return newnode;
 }
 
+static CreateStatsArgument *
+_copyCreateStatsArgument(const CreateStatsArgument *from)
+{
+       CreateStatsArgument *newnode = makeNode(CreateStatsArgument);
+
+       COPY_SCALAR_FIELD(subtype);
+       COPY_NODE_FIELD(elements);
+
+       return newnode;
+}
+
 static CreateFunctionStmt *
 _copyCreateFunctionStmt(const CreateFunctionStmt *from)
 {
@@ -5121,6 +5132,9 @@ copyObjectImpl(const void *from)
                case T_CreateStatsStmt:
                        retval = _copyCreateStatsStmt(from);
                        break;
+               case T_CreateStatsArgument:
+                       retval = _copyCreateStatsArgument(from);
+                       break;
                case T_CreateFunctionStmt:
                        retval = _copyCreateFunctionStmt(from);
                        break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index b5459cd726..abfe982d1e 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1349,8 +1349,8 @@ static bool
 _equalCreateStatsStmt(const CreateStatsStmt *a, const CreateStatsStmt *b)
 {
        COMPARE_NODE_FIELD(defnames);
-       COMPARE_NODE_FIELD(relation);
-       COMPARE_NODE_FIELD(keys);
+       COMPARE_NODE_FIELD(relations);
+       COMPARE_NODE_FIELD(exprs);
        COMPARE_NODE_FIELD(options);
        COMPARE_SCALAR_FIELD(if_not_exists);
 
@@ -1358,6 +1358,15 @@ _equalCreateStatsStmt(const CreateStatsStmt *a, const 
CreateStatsStmt *b)
 }
 
 static bool
+_equalCreateStatsArgument(const CreateStatsArgument *a, const 
CreateStatsArgument *b)
+{
+       COMPARE_SCALAR_FIELD(subtype);
+       COMPARE_NODE_FIELD(elements);
+
+       return true;
+}
+
+static bool
 _equalCreateFunctionStmt(const CreateFunctionStmt *a, const CreateFunctionStmt 
*b)
 {
        COMPARE_SCALAR_FIELD(replace);
@@ -3270,6 +3279,9 @@ equal(const void *a, const void *b)
                case T_CreateStatsStmt:
                        retval = _equalCreateStatsStmt(a, b);
                        break;
+               case T_CreateStatsArgument:
+                       retval = _equalCreateStatsArgument(a, b);
+                       break;
                case T_CreateFunctionStmt:
                        retval = _equalCreateFunctionStmt(a, b);
                        break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 98f67681a7..2334abfb19 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2639,13 +2639,22 @@ _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(relations);
+       WRITE_NODE_FIELD(exprs);
        WRITE_NODE_FIELD(options);
        WRITE_BOOL_FIELD(if_not_exists);
 }
 
 static void
+_outCreateStatsArgument(StringInfo str, const CreateStatsArgument *node)
+{
+       WRITE_NODE_TYPE("CREATESTATSARG");
+
+       WRITE_INT_FIELD(subtype);
+       WRITE_NODE_FIELD(elements);
+}
+
+static void
 _outNotifyStmt(StringInfo str, const NotifyStmt *node)
 {
        WRITE_NODE_TYPE("NOTIFY");
@@ -4051,6 +4060,9 @@ outNode(StringInfo str, const void *obj)
                        case T_CreateStatsStmt:
                                _outCreateStatsStmt(str, obj);
                                break;
+                       case T_CreateStatsArgument:
+                               _outCreateStatsArgument(str, obj);
+                               break;
                        case T_NotifyStmt:
                                _outNotifyStmt(str, obj);
                                break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 65c004c509..f59c38d29d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3836,31 +3836,24 @@ 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 joined tables
  *
  *****************************************************************************/
 
+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->if_not_exists = $2;
 
-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;
-                                               }
+                                       n->relations = $9;
+                                       n->exprs = $7;
+                                       $$ = (Node *)n;
+                               }
                        ;
 
 /*****************************************************************************
diff --git a/src/backend/utils/adt/ruleutils.c 
b/src/backend/utils/adt/ruleutils.c
index cbde1fff01..983b9800cc 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 ce0c9ef54d..f88832e7cb 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4957,9 +4957,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,
@@ -4990,10 +4990,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 13395f5ca6..386af6168f 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 3bd527700e..7853c259a5 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -453,6 +453,21 @@ static const SchemaQuery Query_for_list_of_foreign_tables 
= {
        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
+};
+
 static const SchemaQuery Query_for_list_of_tables = {
        /* catname */
        "pg_catalog.pg_class c",
@@ -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},
@@ -1783,6 +1799,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");
@@ -2119,7 +2139,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};
@@ -2383,6 +2404,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"))
@@ -2589,7 +2623,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/nodes.h b/src/include/nodes/nodes.h
index f59d719923..2c4e86b9a3 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -462,6 +462,7 @@ typedef enum NodeTag
        T_InferClause,
        T_OnConflictClause,
        T_CommonTableExpr,
+       T_CreateStatsArgument,
        T_RoleSpec,
        T_TriggerTransition,
        T_PartitionElem,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 46c23c2530..a1c8aace58 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2689,12 +2689,26 @@ 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       *stat_types;         /* stat types (list of Value strings) */
+       List       *relations;          /* relations to build statistics on */
+       List       *exprs;                      /* Expressions to build 
statistics on */
        List       *options;            /* list of DefElem */
        bool            if_not_exists;  /* do nothing if statistics already 
exists */
 } CreateStatsStmt;
 
+typedef enum CSA_Type
+{
+       CSA_Relations,
+       CSA_Expressions
+} CSA_Type;
+
+typedef struct CreateStatsArgument
+{
+       NodeTag         type;
+       CSA_Type        subtype;
+       List       *elements;                   /* elements (list of Node) */
+} CreateStatsArgument;
+
 /* ----------------------
  *             Create Function Statement
  * ----------------------
diff --git a/src/test/regress/expected/alter_generic.out 
b/src/test/regress/expected/alter_generic.out
index a81a4edfb2..0a00fcf806 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,8 @@ 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 STATISTICS alt_stat1 ON a, b FROM alt_regress_1;
+CREATE STATISTICS alt_stat2 ON a, b FROM alt_regress_1;
 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
diff --git a/src/test/regress/expected/object_address.out 
b/src/test/regress/expected/object_address.out
index 40eeeed3d2..dce85084f3 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 (NOCONNECT, 
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 92ac84ac67..4ccdf21a01 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 88e8d7eb86..8d1cb55a9f 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,8 @@ 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 STATISTICS alt_stat1 ON a, b FROM alt_regress_1;
+CREATE STATISTICS alt_stat2 ON a, b FROM alt_regress_1;
 
 ALTER STATISTICS alt_stat3 RENAME TO alt_stat4;    -- failed (not owner)
 ALTER STATISTICS alt_stat1 RENAME TO alt_stat4;    -- OK
diff --git a/src/test/regress/sql/object_address.sql 
b/src/test/regress/sql/object_address.sql
index 6940392c01..70f5f1c1af 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 (NOCONNECT, 
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 72c7659c4b..4050f33c08 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;
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to