diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index e70ff6816ac..18a343c1abd 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -82,7 +82,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
and like_option is:
-{ INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | IDENTITY | INDEXES | STORAGE | COMMENTS | ALL }
+{ INCLUDING | EXCLUDING } { COMMENTS | CONSTRAINTS | DEFAULTS | IDENTITY | INDEXES | STATISTICS | STORAGE | ALL }
and partition_bound_spec is:
@@ -536,6 +536,10 @@ FROM ( { numeric_literal |
+
+ Extended statistics are copied to the new table if
+ INCLUDING STATISTICS is specified.
+
Indexes, PRIMARY KEY>, UNIQUE>,
and EXCLUDE> constraints on the original table will be
@@ -561,7 +565,7 @@ FROM ( { numeric_literal |
INCLUDING ALL is an abbreviated form of
- INCLUDING DEFAULTS INCLUDING IDENTITY INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS.
+ INCLUDING COMMENTS INCLUDING CONSTRAINTS INCLUDING DEFAULTS INCLUDING IDENTITY INCLUDING INDEXES INCLUDING STATISTICS INCLUDING STORAGE.
Note that unlike INHERITS, columns and
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 84b6fc84a32..9c31508bc77 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1480,7 +1480,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
@@ -1656,6 +1657,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)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index c70a28de4bc..9f963ddee98 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -31,6 +31,11 @@
#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 +56,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 +79,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 +127,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 +306,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);
@@ -405,3 +423,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);
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d0de99baf2..3b9b93f84dd 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3489,12 +3489,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; }
;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 722637b771f..ed7b79d4230 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -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
@@ -121,11 +123,14 @@ static void transformOfType(CreateStmtContext *cxt,
static IndexStmt *generateClonedIndexStmt(CreateStmtContext *cxt,
Relation source_idx,
const AttrNumber *attmap, int attmap_length);
+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);
@@ -237,6 +242,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;
@@ -337,6 +343,11 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
transformCheckConstraints(&cxt, !is_foreign_table ? true : false);
+ /*
+ * Postprocess extended statistics.
+ */
+ transformExtendedStatistics(&cxt);
+
/*
* Output results.
*/
@@ -1214,6 +1225,35 @@ 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);
+ cxt->extstats = lappend(cxt->extstats, stats_stmt);
+
+ /*
+ * We'd like to clone the comments too, but we lack the support
+ * code to do it.
+ */
+ }
+
+ 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
@@ -1581,6 +1621,84 @@ generateClonedIndexStmt(CreateStmtContext *cxt, 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_relid_attribute_name(heapRelid,
+ attnum)));
+ 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->if_not_exists = false;
+
+ /* Clean up */
+ ReleaseSysCache(ht_stats);
+
+ return stats;
+}
+
/*
* get_collation - fetch qualified name of a collation
*
@@ -2133,6 +2251,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
@@ -2709,6 +2839,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;
@@ -2971,6 +3102,9 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
newcmds = lappend(newcmds, newcmd);
}
+ /* Append extended statistic objects */
+ transformExtendedStatistics(&cxt);
+
/* Close rel */
relation_close(rel, NoLock);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ecb6cd02498..cf401dcf821 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -674,6 +674,7 @@ typedef enum TableLikeOption
CREATE_TABLE_LIKE_INDEXES = 1 << 3,
CREATE_TABLE_LIKE_STORAGE = 1 << 4,
CREATE_TABLE_LIKE_COMMENTS = 1 << 5,
+ CREATE_TABLE_LIKE_STATISTICS = 1 << 6,
CREATE_TABLE_LIKE_ALL = PG_INT32_MAX
} TableLikeOption;
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index 3f405c94ce8..d640bce1799 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -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,11 @@ 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
+---------+----------+-------------
+(0 rows)
+
CREATE TABLE inh_error1 () INHERITS (ctlt1, ctlt4);
NOTICE: merging multiple inherited definitions of column "a"
ERROR: inherited column "a" has a storage parameter conflict
diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql
index 557040bbe7d..32d861621bf 100644
--- a/src/test/regress/sql/create_table_like.sql
+++ b/src/test/regress/sql/create_table_like.sql
@@ -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);