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);