aboutsummaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
authorAlvaro Herrera <alvherre@alvh.no-ip.org>2018-03-05 19:37:19 -0300
committerAlvaro Herrera <alvherre@alvh.no-ip.org>2018-03-05 19:37:19 -0300
commit911e6236bab5b1c2240c087e5e8a110acdb724ba (patch)
treee7684c27ddf3c5537cfb54d2ae08043ed1394865 /src/backend
parentbca696ab0bff306a01870306c0dfc9971b079c4e (diff)
downloadpostgresql-911e6236bab5b1c2240c087e5e8a110acdb724ba.tar.gz
postgresql-911e6236bab5b1c2240c087e5e8a110acdb724ba.zip
Clone extended stats in CREATE TABLE (LIKE INCLUDING ALL)
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
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/commands/indexcmds.c5
-rw-r--r--src/backend/commands/statscmds.c163
-rw-r--r--src/backend/parser/gram.y5
-rw-r--r--src/backend/parser/parse_utilcmd.c134
4 files changed, 277 insertions, 30 deletions
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
@@ -149,6 +128,45 @@ CreateStatistics(CreateStatsStmt *stmt)
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
* it so we have to enforce restrictions here. For now, we can convert
@@ -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;
@@ -338,6 +344,11 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
transformCheckConstraints(&cxt, !is_foreign_table ? true : false);
/*
+ * Postprocess extended statistics.
+ */
+ transformExtendedStatistics(&cxt);
+
+ /*
* Output results.
*/
stmt->tableElts = cxt.columns;
@@ -1215,6 +1226,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
* parent before the child is committed.
@@ -1582,6 +1622,84 @@ generateClonedIndexStmt(CreateStmtContext *cxt, 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.
+ */
+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
*
* If collation is InvalidOid or is the default for the given actual_datatype,
@@ -2134,6 +2252,18 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
}
/*
+ * 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);