]> granicus.if.org Git - postgresql/commitdiff
Clone extended stats in CREATE TABLE (LIKE INCLUDING ALL)
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Mon, 5 Mar 2018 22:37:19 +0000 (19:37 -0300)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Mon, 5 Mar 2018 22:37:19 +0000 (19:37 -0300)
The LIKE INCLUDING ALL clause to CREATE TABLE intuitively indicates
cloning of extended statistics on the source table, but it failed to do
so.  Patch it up so that it does.  Also include an INCLUDING STATISTICS
option to the LIKE clause, so that the behavior can be requested
individually, or excluded individually.

While at it, reorder the INCLUDING options, both in code and in docs, in
alphabetical order which makes more sense than feature-implementation
order that was previously used.

Backpatch this to Postgres 10, where extended statistics were
introduced, because this is seen as an oversight in a fresh feature
which is better to get consistent from the get-go instead of changing
only in pg11.

In pg11, comments on statistics objects are cloned too.  In pg10 they
are not, because I (Álvaro) was too coward to change the parse node as
required to support it.  Also, in pg10 I chose not to renumber the
parser symbols for the various INCLUDING options in LIKE, for the same
reason.  Any corresponding user-visible changes (docs) are backpatched,
though.

Reported-by: Stephen Froehlich
Author: David Rowley
Reviewed-by: Álvaro Herrera, Tomas Vondra
Discussion: https://postgr.es/m/CY1PR0601MB1927315B45667A1B679D0FD5E5EF0@CY1PR0601MB1927.namprd06.prod.outlook.com

doc/src/sgml/ref/create_table.sgml
src/backend/commands/indexcmds.c
src/backend/commands/statscmds.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/parser/gram.y
src/backend/parser/parse_utilcmd.c
src/include/nodes/parsenodes.h
src/test/regress/expected/create_table_like.out
src/test/regress/sql/create_table_like.sql

index 338dddd7ccd05a3bb8a668454ca5940a4789b7a0..9e8e9d8f1cd01e802e175c2cf645ed2d38154e96 100644 (file)
@@ -82,7 +82,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 
 <phrase>and <replaceable class="parameter">like_option</replaceable> is:</phrase>
 
-{ INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | IDENTITY | INDEXES | STORAGE | COMMENTS | ALL }
+{ INCLUDING | EXCLUDING } { COMMENTS | CONSTRAINTS | DEFAULTS | IDENTITY | INDEXES | STATISTICS | STORAGE | ALL }
 
 <phrase>and <replaceable class="parameter">partition_bound_spec</replaceable> is:</phrase>
 
@@ -591,6 +591,10 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       No distinction is made between column constraints and table
       constraints.
      </para>
+     <para>
+      Extended statistics are copied to the new table if
+      <literal>INCLUDING STATISTICS</literal> is specified.
+     </para>
      <para>
       Indexes, <literal>PRIMARY KEY</literal>, <literal>UNIQUE</literal>,
       and <literal>EXCLUDE</literal> constraints on the original table will be
@@ -616,7 +620,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
      </para>
      <para>
       <literal>INCLUDING ALL</literal> is an abbreviated form of
-      <literal>INCLUDING DEFAULTS INCLUDING IDENTITY INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS</literal>.
+      <literal>INCLUDING COMMENTS INCLUDING CONSTRAINTS INCLUDING DEFAULTS INCLUDING IDENTITY INCLUDING INDEXES INCLUDING STATISTICS INCLUDING STORAGE</literal>.
      </para>
      <para>
       Note that unlike <literal>INHERITS</literal>, columns and
index 3e48a58dcbbf289d4e5d01f139b4c8e998098b05..504806b25b0ac7ce98e8d4ab5ca462e664af49b5 100644 (file)
@@ -1805,7 +1805,8 @@ GetDefaultOpClass(Oid type_id, Oid am_id)
 /*
  *     makeObjectName()
  *
- *     Create a name for an implicitly created index, sequence, constraint, etc.
+ *     Create a name for an implicitly created index, sequence, constraint,
+ *     extended statistics, etc.
  *
  *     The parameters are typically: the original table name, the original field
  *     name, and a "type" string (such as "seq" or "pkey").    The field name
@@ -1981,6 +1982,8 @@ ChooseIndexName(const char *tabname, Oid namespaceId,
  *
  * We know that less than NAMEDATALEN characters will actually be used,
  * so we can truncate the result once we've generated that many.
+ *
+ * XXX See also ChooseExtendedStatisticNameAddition.
  */
 static char *
 ChooseIndexNameAddition(List *colnames)
index e90a14273b7844479b2408d3be4489c69286b4ce..f46ef01134f7f220636fa36c03cea512fa08f982 100644 (file)
@@ -20,6 +20,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "commands/comment.h"
 #include "commands/defrem.h"
 #include "miscadmin.h"
 #include "statistics/statistics.h"
 #include "utils/typcache.h"
 
 
+static char *ChooseExtendedStatisticName(const char *name1, const char *name2,
+                                                       const char *label, Oid namespaceid);
+static char *ChooseExtendedStatisticNameAddition(List *exprs);
+
+
 /* qsort comparator for the attnums in CreateStatistics */
 static int
 compare_int16(const void *a, const void *b)
@@ -51,7 +57,6 @@ CreateStatistics(CreateStatsStmt *stmt)
        int16           attnums[STATS_MAX_DIMENSIONS];
        int                     numcols = 0;
        char       *namestr;
-       NameData        stxname;
        Oid                     statoid;
        Oid                     namespaceId;
        Oid                     stxowner = GetUserId();
@@ -75,31 +80,6 @@ CreateStatistics(CreateStatsStmt *stmt)
 
        Assert(IsA(stmt, CreateStatsStmt));
 
-       /* resolve the pieces of the name (namespace etc.) */
-       namespaceId = QualifiedNameGetCreationNamespace(stmt->defnames, &namestr);
-       namestrcpy(&stxname, namestr);
-
-       /*
-        * Deal with the possibility that the statistics object already exists.
-        */
-       if (SearchSysCacheExists2(STATEXTNAMENSP,
-                                                         NameGetDatum(&stxname),
-                                                         ObjectIdGetDatum(namespaceId)))
-       {
-               if (stmt->if_not_exists)
-               {
-                       ereport(NOTICE,
-                                       (errcode(ERRCODE_DUPLICATE_OBJECT),
-                                        errmsg("statistics object \"%s\" already exists, skipping",
-                                                       namestr)));
-                       return InvalidObjectAddress;
-               }
-
-               ereport(ERROR,
-                               (errcode(ERRCODE_DUPLICATE_OBJECT),
-                                errmsg("statistics object \"%s\" already exists", namestr)));
-       }
-
        /*
         * 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
@@ -148,6 +128,45 @@ CreateStatistics(CreateStatsStmt *stmt)
        Assert(rel);
        relid = RelationGetRelid(rel);
 
+       /*
+        * If the node has a name, split it up and determine creation namespace.
+        * If not (a possibility not considered by the grammar, but one which can
+        * occur via the "CREATE TABLE ... (LIKE)" command), then we put the
+        * object in the same namespace as the relation, and cons up a name for it.
+        */
+       if (stmt->defnames)
+               namespaceId = QualifiedNameGetCreationNamespace(stmt->defnames, &namestr);
+       else
+       {
+               namespaceId = RelationGetNamespace(rel);
+               namestr = ChooseExtendedStatisticName(RelationGetRelationName(rel),
+                                                                                         ChooseExtendedStatisticNameAddition(stmt->exprs),
+                                                                                         "stat",
+                                                                                         namespaceId);
+       }
+
+       /*
+        * Deal with the possibility that the statistics object already exists.
+        */
+       if (SearchSysCacheExists2(STATEXTNAMENSP,
+                                                         CStringGetDatum(namestr),
+                                                         ObjectIdGetDatum(namespaceId)))
+       {
+               if (stmt->if_not_exists)
+               {
+                       ereport(NOTICE,
+                                       (errcode(ERRCODE_DUPLICATE_OBJECT),
+                                        errmsg("statistics object \"%s\" already exists, skipping",
+                                                       namestr)));
+                       relation_close(rel, NoLock);
+                       return InvalidObjectAddress;
+               }
+
+               ereport(ERROR,
+                               (errcode(ERRCODE_DUPLICATE_OBJECT),
+                                errmsg("statistics object \"%s\" already exists", namestr)));
+       }
+
        /*
         * Currently, we only allow simple column references in the expression
         * list.  That will change someday, and again the grammar already supports
@@ -288,7 +307,7 @@ CreateStatistics(CreateStatsStmt *stmt)
        memset(values, 0, sizeof(values));
        memset(nulls, false, sizeof(nulls));
        values[Anum_pg_statistic_ext_stxrelid - 1] = ObjectIdGetDatum(relid);
-       values[Anum_pg_statistic_ext_stxname - 1] = NameGetDatum(&stxname);
+       values[Anum_pg_statistic_ext_stxname - 1] = CStringGetDatum(namestr);
        values[Anum_pg_statistic_ext_stxnamespace - 1] = ObjectIdGetDatum(namespaceId);
        values[Anum_pg_statistic_ext_stxowner - 1] = ObjectIdGetDatum(stxowner);
        values[Anum_pg_statistic_ext_stxkeys - 1] = PointerGetDatum(stxkeys);
@@ -340,6 +359,11 @@ CreateStatistics(CreateStatsStmt *stmt)
         * STATISTICS, which is more work than it seems worth.
         */
 
+       /* Add any requested comment */
+       if (stmt->stxcomment != NULL)
+               CreateComments(statoid, StatisticExtRelationId, 0,
+                                          stmt->stxcomment);
+
        /* Return stats object's address */
        return myself;
 }
@@ -405,3 +429,94 @@ UpdateStatisticsForTypeChange(Oid statsOid, Oid relationOid, int attnum,
         * Future types of extended stats will likely require us to work harder.
         */
 }
+
+/*
+ * Select a nonconflicting name for a new statistics.
+ *
+ * name1, name2, and label are used the same way as for makeObjectName(),
+ * except that the label can't be NULL; digits will be appended to the label
+ * if needed to create a name that is unique within the specified namespace.
+ *
+ * Returns a palloc'd string.
+ *
+ * Note: it is theoretically possible to get a collision anyway, if someone
+ * else chooses the same name concurrently.  This is fairly unlikely to be
+ * a problem in practice, especially if one is holding a share update
+ * exclusive lock on the relation identified by name1.  However, if choosing
+ * multiple names within a single command, you'd better create the new object
+ * and do CommandCounterIncrement before choosing the next one!
+ */
+static char *
+ChooseExtendedStatisticName(const char *name1, const char *name2,
+                                                       const char *label, Oid namespaceid)
+{
+       int                     pass = 0;
+       char       *stxname = NULL;
+       char            modlabel[NAMEDATALEN];
+
+       /* try the unmodified label first */
+       StrNCpy(modlabel, label, sizeof(modlabel));
+
+       for (;;)
+       {
+               Oid             existingstats;
+
+               stxname = makeObjectName(name1, name2, modlabel);
+
+               existingstats = GetSysCacheOid2(STATEXTNAMENSP,
+                                                                               PointerGetDatum(stxname),
+                                                                               ObjectIdGetDatum(namespaceid));
+               if (!OidIsValid(existingstats))
+                       break;
+
+               /* found a conflict, so try a new name component */
+               pfree(stxname);
+               snprintf(modlabel, sizeof(modlabel), "%s%d", label, ++pass);
+       }
+
+       return stxname;
+}
+
+/*
+ * Generate "name2" for a new statistics given the list of column names for it
+ * This will be passed to ChooseExtendedStatisticName along with the parent
+ * table name and a suitable label.
+ *
+ * We know that less than NAMEDATALEN characters will actually be used,
+ * so we can truncate the result once we've generated that many.
+ *
+ * XXX see also ChooseIndexNameAddition.
+ */
+static char *
+ChooseExtendedStatisticNameAddition(List *exprs)
+{
+       char            buf[NAMEDATALEN * 2];
+       int                     buflen = 0;
+       ListCell   *lc;
+
+       buf[0] = '\0';
+       foreach(lc, exprs)
+       {
+               ColumnRef *cref = (ColumnRef *) lfirst(lc);
+               const char *name;
+
+               /* It should be one of these, but just skip if it happens not to be */
+               if (!IsA(cref, ColumnRef))
+                       continue;
+
+               name = strVal((Value *) linitial(cref->fields));
+
+               if (buflen > 0)
+                       buf[buflen++] = '_';    /* insert _ between names */
+
+               /*
+                * At this point we have buflen <= NAMEDATALEN.  name should be less
+                * than NAMEDATALEN already, but use strlcpy for paranoia.
+                */
+               strlcpy(buf + buflen, name, NAMEDATALEN);
+               buflen += strlen(buf + buflen);
+               if (buflen >= NAMEDATALEN)
+                       break;
+       }
+       return pstrdup(buf);
+}
index 266a3ef8ef6d6af84197bd7d8c94737a8f160784..f84da801c6d27c212bae8bf95dcff7a8432e5d93 100644 (file)
@@ -3424,6 +3424,7 @@ _copyCreateStatsStmt(const CreateStatsStmt *from)
        COPY_NODE_FIELD(stat_types);
        COPY_NODE_FIELD(exprs);
        COPY_NODE_FIELD(relations);
+       COPY_STRING_FIELD(stxcomment);
        COPY_SCALAR_FIELD(if_not_exists);
 
        return newnode;
index bbffc878423630b9d8bf52f2bac6d29cd919bd3e..ee8d925db14daa9bb0337b19e9f333ac3abfff69 100644 (file)
@@ -1363,6 +1363,7 @@ _equalCreateStatsStmt(const CreateStatsStmt *a, const CreateStatsStmt *b)
        COMPARE_NODE_FIELD(stat_types);
        COMPARE_NODE_FIELD(exprs);
        COMPARE_NODE_FIELD(relations);
+       COMPARE_STRING_FIELD(stxcomment);
        COMPARE_SCALAR_FIELD(if_not_exists);
 
        return true;
index 011d2a3fa93bf4a2b0abc6d519d6521b16d060df..1785ea391863356c987269fefe975d498b592a09 100644 (file)
@@ -2687,6 +2687,7 @@ _outCreateStatsStmt(StringInfo str, const CreateStatsStmt *node)
        WRITE_NODE_FIELD(stat_types);
        WRITE_NODE_FIELD(exprs);
        WRITE_NODE_FIELD(relations);
+       WRITE_STRING_FIELD(stxcomment);
        WRITE_BOOL_FIELD(if_not_exists);
 }
 
index d99f2be2c975a75cb3b194444149c49ba302f278..8a2e52acb45d59cb02ce22afdaeda284f7c00812 100644 (file)
@@ -3636,12 +3636,13 @@ TableLikeOptionList:
                ;
 
 TableLikeOption:
-                               DEFAULTS                        { $$ = CREATE_TABLE_LIKE_DEFAULTS; }
+                               COMMENTS                        { $$ = CREATE_TABLE_LIKE_COMMENTS; }
                                | CONSTRAINTS           { $$ = CREATE_TABLE_LIKE_CONSTRAINTS; }
+                               | DEFAULTS                      { $$ = CREATE_TABLE_LIKE_DEFAULTS; }
                                | IDENTITY_P            { $$ = CREATE_TABLE_LIKE_IDENTITY; }
                                | INDEXES                       { $$ = CREATE_TABLE_LIKE_INDEXES; }
+                               | STATISTICS            { $$ = CREATE_TABLE_LIKE_STATISTICS; }
                                | STORAGE                       { $$ = CREATE_TABLE_LIKE_STORAGE; }
-                               | COMMENTS                      { $$ = CREATE_TABLE_LIKE_COMMENTS; }
                                | ALL                           { $$ = CREATE_TABLE_LIKE_ALL; }
                ;
 
@@ -3980,6 +3981,7 @@ CreateStatsStmt:
                                        n->stat_types = $4;
                                        n->exprs = $6;
                                        n->relations = $8;
+                                       n->stxcomment = NULL;
                                        n->if_not_exists = false;
                                        $$ = (Node *)n;
                                }
@@ -3991,6 +3993,7 @@ CreateStatsStmt:
                                        n->stat_types = $7;
                                        n->exprs = $9;
                                        n->relations = $11;
+                                       n->stxcomment = NULL;
                                        n->if_not_exists = true;
                                        $$ = (Node *)n;
                                }
index 6029eb13d7fad0f8dec28906e02cb555d8e60efa..8c7bd686397d737e4ef1b567ebbfe4e66ba5db17 100644 (file)
@@ -39,6 +39,7 @@
 #include "catalog/pg_constraint_fn.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_type.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -85,6 +86,7 @@ typedef struct
        List       *fkconstraints;      /* FOREIGN KEY constraints */
        List       *ixconstraints;      /* index-creating constraints */
        List       *inh_indexes;        /* cloned indexes from INCLUDING INDEXES */
+       List       *extstats;           /* cloned extended statistics */
        List       *blist;                      /* "before list" of things to do before
                                                                 * creating the table */
        List       *alist;                      /* "after list" of things to do after creating
@@ -118,11 +120,14 @@ static void transformTableLikeClause(CreateStmtContext *cxt,
                                                 TableLikeClause *table_like_clause);
 static void transformOfType(CreateStmtContext *cxt,
                                TypeName *ofTypename);
+static CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel,
+                                                  Oid heapRelid, Oid source_statsid);
 static List *get_collation(Oid collation, Oid actual_datatype);
 static List *get_opclass(Oid opclass, Oid actual_datatype);
 static void transformIndexConstraints(CreateStmtContext *cxt);
 static IndexStmt *transformIndexConstraint(Constraint *constraint,
                                                 CreateStmtContext *cxt);
+static void transformExtendedStatistics(CreateStmtContext *cxt);
 static void transformFKConstraints(CreateStmtContext *cxt,
                                           bool skipValidation,
                                           bool isAddConstraint);
@@ -234,6 +239,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
        cxt.fkconstraints = NIL;
        cxt.ixconstraints = NIL;
        cxt.inh_indexes = NIL;
+       cxt.extstats = NIL;
        cxt.blist = NIL;
        cxt.alist = NIL;
        cxt.pkey = NULL;
@@ -334,6 +340,11 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
         */
        transformCheckConstraints(&cxt, !is_foreign_table ? true : false);
 
+       /*
+        * Postprocess extended statistics.
+        */
+       transformExtendedStatistics(&cxt);
+
        /*
         * Output results.
         */
@@ -1189,6 +1200,43 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
                }
        }
 
+       /*
+        * Likewise, copy extended statistics if requested
+        */
+       if (table_like_clause->options & CREATE_TABLE_LIKE_STATISTICS)
+       {
+               List               *parent_extstats;
+               ListCell           *l;
+
+               parent_extstats = RelationGetStatExtList(relation);
+
+               foreach(l, parent_extstats)
+               {
+                       Oid             parent_stat_oid = lfirst_oid(l);
+                       CreateStatsStmt *stats_stmt;
+
+                       stats_stmt = generateClonedExtStatsStmt(cxt->relation,
+                                                                                                       RelationGetRelid(relation),
+                                                                                                       parent_stat_oid);
+
+                       /* Copy comment on statistics object, if requested */
+                       if (table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS)
+                       {
+                               comment = GetComment(parent_stat_oid, StatisticExtRelationId, 0);
+
+                               /*
+                                * We make use of CreateStatsStmt's stxcomment option, so as
+                                * not to need to know now what name the statistics will have.
+                                */
+                               stats_stmt->stxcomment = comment;
+                       }
+
+                       cxt->extstats = lappend(cxt->extstats, stats_stmt);
+               }
+
+               list_free(parent_extstats);
+       }
+
        /*
         * Close the parent rel, but keep our AccessShareLock on it until xact
         * commit.  That will prevent someone else from deleting or ALTERing the
@@ -1566,6 +1614,85 @@ generateClonedIndexStmt(RangeVar *heapRel, Oid heapRelid, Relation source_idx,
        return index;
 }
 
+/*
+ * Generate a CreateStatsStmt node using information from an already existing
+ * extended statistic "source_statsid", for the rel identified by heapRel and
+ * heapRelid.
+ */
+static CreateStatsStmt *
+generateClonedExtStatsStmt(RangeVar *heapRel, Oid heapRelid,
+                                                  Oid source_statsid)
+{
+       HeapTuple               ht_stats;
+       Form_pg_statistic_ext statsrec;
+       CreateStatsStmt *stats;
+       List               *stat_types = NIL;
+       List               *def_names = NIL;
+       bool                     isnull;
+       Datum                   datum;
+       ArrayType          *arr;
+       char               *enabled;
+       int                             i;
+
+       Assert(OidIsValid(heapRelid));
+       Assert(heapRel != NULL);
+
+       /*
+        * Fetch pg_statistic_ext tuple of source statistics object.
+        */
+       ht_stats = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(source_statsid));
+       if (!HeapTupleIsValid(ht_stats))
+               elog(ERROR, "cache lookup failed for statistics object %u", source_statsid);
+       statsrec = (Form_pg_statistic_ext) GETSTRUCT(ht_stats);
+
+       /* Determine which statistics types exist */
+       datum = SysCacheGetAttr(STATEXTOID, ht_stats,
+                                                       Anum_pg_statistic_ext_stxkind, &isnull);
+       Assert(!isnull);
+       arr = DatumGetArrayTypeP(datum);
+       if (ARR_NDIM(arr) != 1 ||
+               ARR_HASNULL(arr) ||
+               ARR_ELEMTYPE(arr) != CHAROID)
+               elog(ERROR, "stxkind is not a 1-D char array");
+       enabled = (char *) ARR_DATA_PTR(arr);
+       for (i = 0; i < ARR_DIMS(arr)[0]; i++)
+       {
+               if (enabled[i] == STATS_EXT_NDISTINCT)
+                       stat_types = lappend(stat_types, makeString("ndistinct"));
+               else if (enabled[i] == STATS_EXT_DEPENDENCIES)
+                       stat_types = lappend(stat_types, makeString("dependencies"));
+               else
+                       elog(ERROR, "unrecognized statistics kind %c", enabled[i]);
+       }
+
+       /* Determine which columns the statistics are on */
+       for (i = 0; i < statsrec->stxkeys.dim1; i++)
+       {
+               ColumnRef  *cref = makeNode(ColumnRef);
+               AttrNumber      attnum = statsrec->stxkeys.values[i];
+
+               cref->fields = list_make1(makeString(get_attname(heapRelid,
+                                                                                                                attnum, false)));
+               cref->location = -1;
+
+               def_names = lappend(def_names, cref);
+       }
+
+       /* finally, build the output node */
+       stats = makeNode(CreateStatsStmt);
+       stats->defnames = NULL;
+       stats->stat_types = stat_types;
+       stats->exprs = def_names;
+       stats->relations = list_make1(heapRel);
+       stats->stxcomment = NULL;
+       stats->if_not_exists = false;
+
+       /* Clean up */
+       ReleaseSysCache(ht_stats);
+
+       return stats;
+}
+
 /*
  * get_collation               - fetch qualified name of a collation
  *
@@ -2119,6 +2246,18 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
        return index;
 }
 
+/*
+ * transformExtendedStatistics
+ *     Handle extended statistic objects
+ *
+ * Right now, there's nothing to do here, so we just copy the list.
+ */
+static void
+transformExtendedStatistics(CreateStmtContext *cxt)
+{
+       cxt->alist = list_concat(cxt->alist, cxt->extstats);
+}
+
 /*
  * transformCheckConstraints
  *             handle CHECK constraints
@@ -2695,6 +2834,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
        cxt.fkconstraints = NIL;
        cxt.ixconstraints = NIL;
        cxt.inh_indexes = NIL;
+       cxt.extstats = NIL;
        cxt.blist = NIL;
        cxt.alist = NIL;
        cxt.pkey = NULL;
@@ -2957,6 +3097,9 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                newcmds = lappend(newcmds, newcmd);
        }
 
+       /* Append extended statistic objects */
+       transformExtendedStatistics(&cxt);
+
        /* Close rel */
        relation_close(rel, NoLock);
 
index ac292bc6e7a8b13c127518223db6e538975a14fa..f668cbad346f27ce3f3148be9c705f0554948d45 100644 (file)
@@ -674,12 +674,13 @@ typedef struct TableLikeClause
 
 typedef enum TableLikeOption
 {
-       CREATE_TABLE_LIKE_DEFAULTS = 1 << 0,
+       CREATE_TABLE_LIKE_COMMENTS = 1 << 0,
        CREATE_TABLE_LIKE_CONSTRAINTS = 1 << 1,
-       CREATE_TABLE_LIKE_IDENTITY = 1 << 2,
-       CREATE_TABLE_LIKE_INDEXES = 1 << 3,
-       CREATE_TABLE_LIKE_STORAGE = 1 << 4,
-       CREATE_TABLE_LIKE_COMMENTS = 1 << 5,
+       CREATE_TABLE_LIKE_DEFAULTS = 1 << 2,
+       CREATE_TABLE_LIKE_IDENTITY = 1 << 3,
+       CREATE_TABLE_LIKE_INDEXES = 1 << 4,
+       CREATE_TABLE_LIKE_STATISTICS = 1 << 5,
+       CREATE_TABLE_LIKE_STORAGE = 1 << 6,
        CREATE_TABLE_LIKE_ALL = PG_INT32_MAX
 } TableLikeOption;
 
@@ -2741,6 +2742,7 @@ typedef struct CreateStatsStmt
        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) */
+       char       *stxcomment;         /* comment to apply to stats, or NULL */
        bool            if_not_exists;  /* do nothing if stats name already exists */
 } CreateStatsStmt;
 
index 3f405c94ce8a19b5250f57db7744b96f64a9a98b..52ff18c8ca25a5e7be6ddbaf88fa2851805e2b02 100644 (file)
@@ -137,6 +137,8 @@ DROP TABLE inhz;
 CREATE TABLE ctlt1 (a text CHECK (length(a) > 2) PRIMARY KEY, b text);
 CREATE INDEX ctlt1_b_key ON ctlt1 (b);
 CREATE INDEX ctlt1_fnidx ON ctlt1 ((a || b));
+CREATE STATISTICS ctlt1_a_b_stat ON a,b FROM ctlt1;
+COMMENT ON STATISTICS ctlt1_a_b_stat IS 'ab stats';
 COMMENT ON COLUMN ctlt1.a IS 'A';
 COMMENT ON COLUMN ctlt1.b IS 'B';
 COMMENT ON CONSTRAINT ctlt1_a_check ON ctlt1 IS 't1_a_check';
@@ -240,6 +242,8 @@ Indexes:
     "ctlt_all_expr_idx" btree ((a || b))
 Check constraints:
     "ctlt1_a_check" CHECK (length(a) > 2)
+Statistics objects:
+    "public"."ctlt_all_a_b_stat" (ndistinct, dependencies) ON a, b FROM ctlt_all
 
 SELECT c.relname, objsubid, description FROM pg_description, pg_index i, pg_class c WHERE classoid = 'pg_class'::regclass AND objoid = i.indexrelid AND c.oid = i.indexrelid AND i.indrelid = 'ctlt_all'::regclass ORDER BY c.relname, objsubid;
     relname     | objsubid | description 
@@ -248,6 +252,12 @@ SELECT c.relname, objsubid, description FROM pg_description, pg_index i, pg_clas
  ctlt_all_pkey  |        0 | index pkey
 (2 rows)
 
+SELECT s.stxname, objsubid, description FROM pg_description, pg_statistic_ext s WHERE classoid = 'pg_statistic_ext'::regclass AND objoid = s.oid AND s.stxrelid = 'ctlt_all'::regclass ORDER BY s.stxname, objsubid;
+      stxname      | objsubid | description 
+-------------------+----------+-------------
+ ctlt_all_a_b_stat |        0 | ab stats
+(1 row)
+
 CREATE TABLE inh_error1 () INHERITS (ctlt1, ctlt4);
 NOTICE:  merging multiple inherited definitions of column "a"
 ERROR:  inherited column "a" has a storage parameter conflict
index 557040bbe7d374883300775fdff22b8e31743931..32d861621bf5b3955b8b7f23c984cb1075e3f723 100644 (file)
@@ -71,6 +71,8 @@ DROP TABLE inhz;
 CREATE TABLE ctlt1 (a text CHECK (length(a) > 2) PRIMARY KEY, b text);
 CREATE INDEX ctlt1_b_key ON ctlt1 (b);
 CREATE INDEX ctlt1_fnidx ON ctlt1 ((a || b));
+CREATE STATISTICS ctlt1_a_b_stat ON a,b FROM ctlt1;
+COMMENT ON STATISTICS ctlt1_a_b_stat IS 'ab stats';
 COMMENT ON COLUMN ctlt1.a IS 'A';
 COMMENT ON COLUMN ctlt1.b IS 'B';
 COMMENT ON CONSTRAINT ctlt1_a_check ON ctlt1 IS 't1_a_check';
@@ -108,6 +110,7 @@ SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_con
 CREATE TABLE ctlt_all (LIKE ctlt1 INCLUDING ALL);
 \d+ ctlt_all
 SELECT c.relname, objsubid, description FROM pg_description, pg_index i, pg_class c WHERE classoid = 'pg_class'::regclass AND objoid = i.indexrelid AND c.oid = i.indexrelid AND i.indrelid = 'ctlt_all'::regclass ORDER BY c.relname, objsubid;
+SELECT s.stxname, objsubid, description FROM pg_description, pg_statistic_ext s WHERE classoid = 'pg_statistic_ext'::regclass AND objoid = s.oid AND s.stxrelid = 'ctlt_all'::regclass ORDER BY s.stxname, objsubid;
 
 CREATE TABLE inh_error1 () INHERITS (ctlt1, ctlt4);
 CREATE TABLE inh_error2 (LIKE ctlt4 INCLUDING STORAGE) INHERITS (ctlt1);