diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index a0c9a6d..8af6ba9 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -593,6 +593,10 @@ WITH ( MODULUS numeric_literal, REM constraints. + 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 created on the new table only if INCLUDING INDEXES @@ -617,7 +621,7 @@ WITH ( MODULUS numeric_literal, REM INCLUDING ALL is an abbreviated form of - INCLUDING DEFAULTS INCLUDING IDENTITY INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS. + INCLUDING DEFAULTS INCLUDING IDENTITY INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING STATISTICS INCLUDING COMMENTS. Note that unlike INHERITS, columns and diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index a9461a4..bdf27b6 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -1699,7 +1699,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 diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e90a142..687000e 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -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" @@ -43,6 +44,97 @@ compare_int16(const void *a, const void *b) } /* + * 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. + * + * 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! + * + * Returns a palloc'd string. + */ +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. + */ +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); +} + + +/* * CREATE STATISTICS */ ObjectAddress @@ -75,31 +167,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 +215,46 @@ CreateStatistics(CreateStatsStmt *stmt) Assert(rel); relid = RelationGetRelid(rel); + /* resolve the pieces of the name (namespace etc.) */ + if (stmt->defnames) + namespaceId = QualifiedNameGetCreationNamespace(stmt->defnames, &namestr); + else + { + /* + * The syntax does not allow the name to be empty, but the command + * might be coming from somewhere like: + * CREATE TABLE .. (LIKE .. INCLUDING ALL); + */ + namespaceId = RelationGetNamespace(rel); + namestr = ChooseExtendedStatisticName(RelationGetRelationName(rel), + ChooseExtendedStatisticNameAddition(stmt->exprs), + "stat", /* XXX What should this be? */ + namespaceId); + } + 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))); + 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 @@ -340,6 +447,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; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 459a227..282c4bc 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -3638,6 +3638,7 @@ TableLikeOption: | INDEXES { $$ = CREATE_TABLE_LIKE_INDEXES; } | STORAGE { $$ = CREATE_TABLE_LIKE_STORAGE; } | COMMENTS { $$ = CREATE_TABLE_LIKE_COMMENTS; } + | STATISTICS { $$ = CREATE_TABLE_LIKE_STATISTICS; } | ALL { $$ = CREATE_TABLE_LIKE_ALL; } ; @@ -3976,6 +3977,7 @@ CreateStatsStmt: n->stat_types = $4; n->exprs = $6; n->relations = $8; + n->stxcomment = NULL; n->if_not_exists = false; $$ = (Node *)n; } @@ -3987,6 +3989,7 @@ CreateStatsStmt: n->stat_types = $7; n->exprs = $9; n->relations = $11; + n->stxcomment = NULL; n->if_not_exists = true; $$ = (Node *)n; } diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 5afb363..8afa271 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 @@ -123,6 +125,7 @@ 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 +237,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; @@ -324,6 +328,8 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) */ transformIndexConstraints(&cxt); + transformExtendedStatistics(&cxt); + /* * Postprocess foreign-key constraints. */ @@ -1205,6 +1211,37 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla } } + if (table_like_clause->options & CREATE_TABLE_LIKE_STATISTICS) + { + List *parent_extstats; + ListCell *l; + CreateStatsStmt *stats_stmt; + + parent_extstats = RelationGetStatExtList(relation); + + foreach(l, parent_extstats) + { + Oid parent_stat_oid = lfirst_oid(l); + + stats_stmt = generateClonedExtStatsStmt(cxt->relation, + RelationGetRelid(relation), + parent_stat_oid); + + 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); + } + } + /* * Close the parent rel, but keep our AccessShareLock on it until xact * commit. That will prevent someone else from deleting or ALTERing the @@ -1580,6 +1617,96 @@ generateClonedIndexStmt(RangeVar *heapRel, Oid heapRelid, Relation source_idx, } /* + * Generate a CreateStatsStmt node using information from an already existing + * extended statistic "source_statsid", for the rel identified by heapRel and + * heapRelid. + */ +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 statistic. + */ + 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); + HeapTuple attTuple; + AttrNumber attnum = statsrec->stxkeys.values[i]; + Form_pg_attribute attributeForm; + + attTuple = SearchSysCache2(ATTNUM, + ObjectIdGetDatum(heapRelid), + Int16GetDatum(attnum)); + if (!HeapTupleIsValid(attTuple)) + elog(ERROR, "cache lookup failed for attribute %d of relation %u", + attnum, heapRelid); + + attributeForm = (Form_pg_attribute) GETSTRUCT(attTuple); + + cref->fields = list_make1(makeString(NameStr(attributeForm->attname))); + + ReleaseSysCache(attTuple); + + def_names = lappend(def_names, cref); + } + + /* Begin building the CreateStatsStmt */ + stats = makeNode(CreateStatsStmt); + stats->defnames = NULL; + stats->stat_types = stat_types; + stats->exprs = def_names; + stats->relations = list_make1(heapRel); + stats->stxcomment; + stats->if_not_exists = false; + + /* Clean up */ + ReleaseSysCache(ht_stats); + + return stats; +} + +/* * get_collation - fetch qualified name of a collation * * If collation is InvalidOid or is the default for the given actual_datatype, @@ -2132,6 +2259,15 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt) return index; } +static void +transformExtendedStatistics(CreateStmtContext *cxt) +{ + ListCell *lc; + + foreach(lc, cxt->extstats) + cxt->alist = lappend(cxt->alist, lfirst(lc)); +} + /* * transformCheckConstraints * handle CHECK constraints @@ -2708,6 +2844,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; @@ -2970,6 +3107,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, newcmds = lappend(newcmds, newcmd); } + transformExtendedStatistics(&cxt); + /* Close rel */ relation_close(rel, NoLock); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index bbacbe1..70c9943 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -672,6 +672,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; @@ -2725,6 +2726,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; diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 64aa823..29ce8d7 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -30,5 +30,8 @@ extern PartitionBoundSpec *transformPartitionBound(ParseState *pstate, Relation extern IndexStmt *generateClonedIndexStmt(RangeVar *heapRel, Oid heapOid, Relation source_idx, const AttrNumber *attmap, int attmap_length); +extern CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel, + Oid heapRelid, + Oid source_statsid); #endif /* PARSE_UTILCMD_H */ diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out index 3f405c9..52ff18c 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,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 diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql index 557040b..32d8616 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);