Skip to content

Commit 911e623

Browse files
committed
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
1 parent bca696a commit 911e623

File tree

8 files changed

+296
-32
lines changed

8 files changed

+296
-32
lines changed

doc/src/sgml/ref/create_table.sgml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
8282

8383
<phrase>and <replaceable class="PARAMETER">like_option</replaceable> is:</phrase>
8484

85-
{ INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | IDENTITY | INDEXES | STORAGE | COMMENTS | ALL }
85+
{ INCLUDING | EXCLUDING } { COMMENTS | CONSTRAINTS | DEFAULTS | IDENTITY | INDEXES | STATISTICS | STORAGE | ALL }
8686

8787
<phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase>
8888

@@ -536,6 +536,10 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
536536
No distinction is made between column constraints and table
537537
constraints.
538538
</para>
539+
<para>
540+
Extended statistics are copied to the new table if
541+
<literal>INCLUDING STATISTICS</literal> is specified.
542+
</para>
539543
<para>
540544
Indexes, <literal>PRIMARY KEY</>, <literal>UNIQUE</>,
541545
and <literal>EXCLUDE</> constraints on the original table will be
@@ -561,7 +565,7 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
561565
</para>
562566
<para>
563567
<literal>INCLUDING ALL</literal> is an abbreviated form of
564-
<literal>INCLUDING DEFAULTS INCLUDING IDENTITY INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS</literal>.
568+
<literal>INCLUDING COMMENTS INCLUDING CONSTRAINTS INCLUDING DEFAULTS INCLUDING IDENTITY INCLUDING INDEXES INCLUDING STATISTICS INCLUDING STORAGE</literal>.
565569
</para>
566570
<para>
567571
Note that unlike <literal>INHERITS</literal>, columns and

src/backend/commands/indexcmds.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1480,7 +1480,8 @@ GetDefaultOpClass(Oid type_id, Oid am_id)
14801480
/*
14811481
* makeObjectName()
14821482
*
1483-
* Create a name for an implicitly created index, sequence, constraint, etc.
1483+
* Create a name for an implicitly created index, sequence, constraint,
1484+
* extended statistics, etc.
14841485
*
14851486
* The parameters are typically: the original table name, the original field
14861487
* name, and a "type" string (such as "seq" or "pkey"). The field name
@@ -1656,6 +1657,8 @@ ChooseIndexName(const char *tabname, Oid namespaceId,
16561657
*
16571658
* We know that less than NAMEDATALEN characters will actually be used,
16581659
* so we can truncate the result once we've generated that many.
1660+
*
1661+
* XXX See also ChooseExtendedStatisticNameAddition.
16591662
*/
16601663
static char *
16611664
ChooseIndexNameAddition(List *colnames)

src/backend/commands/statscmds.c

Lines changed: 136 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
3131
#include "utils/typcache.h"
3232

3333

34+
static char *ChooseExtendedStatisticName(const char *name1, const char *name2,
35+
const char *label, Oid namespaceid);
36+
static char *ChooseExtendedStatisticNameAddition(List *exprs);
37+
38+
3439
/* qsort comparator for the attnums in CreateStatistics */
3540
static int
3641
compare_int16(const void *a, const void *b)
@@ -51,7 +56,6 @@ CreateStatistics(CreateStatsStmt *stmt)
5156
int16 attnums[STATS_MAX_DIMENSIONS];
5257
int numcols = 0;
5358
char *namestr;
54-
NameData stxname;
5559
Oid statoid;
5660
Oid namespaceId;
5761
Oid stxowner = GetUserId();
@@ -75,31 +79,6 @@ CreateStatistics(CreateStatsStmt *stmt)
7579

7680
Assert(IsA(stmt, CreateStatsStmt));
7781

78-
/* resolve the pieces of the name (namespace etc.) */
79-
namespaceId = QualifiedNameGetCreationNamespace(stmt->defnames, &namestr);
80-
namestrcpy(&stxname, namestr);
81-
82-
/*
83-
* Deal with the possibility that the statistics object already exists.
84-
*/
85-
if (SearchSysCacheExists2(STATEXTNAMENSP,
86-
NameGetDatum(&stxname),
87-
ObjectIdGetDatum(namespaceId)))
88-
{
89-
if (stmt->if_not_exists)
90-
{
91-
ereport(NOTICE,
92-
(errcode(ERRCODE_DUPLICATE_OBJECT),
93-
errmsg("statistics object \"%s\" already exists, skipping",
94-
namestr)));
95-
return InvalidObjectAddress;
96-
}
97-
98-
ereport(ERROR,
99-
(errcode(ERRCODE_DUPLICATE_OBJECT),
100-
errmsg("statistics object \"%s\" already exists", namestr)));
101-
}
102-
10382
/*
10483
* Examine the FROM clause. Currently, we only allow it to be a single
10584
* simple table, but later we'll probably allow multiple tables and JOIN
@@ -148,6 +127,45 @@ CreateStatistics(CreateStatsStmt *stmt)
148127
Assert(rel);
149128
relid = RelationGetRelid(rel);
150129

130+
/*
131+
* If the node has a name, split it up and determine creation namespace.
132+
* If not (a possibility not considered by the grammar, but one which can
133+
* occur via the "CREATE TABLE ... (LIKE)" command), then we put the
134+
* object in the same namespace as the relation, and cons up a name for it.
135+
*/
136+
if (stmt->defnames)
137+
namespaceId = QualifiedNameGetCreationNamespace(stmt->defnames, &namestr);
138+
else
139+
{
140+
namespaceId = RelationGetNamespace(rel);
141+
namestr = ChooseExtendedStatisticName(RelationGetRelationName(rel),
142+
ChooseExtendedStatisticNameAddition(stmt->exprs),
143+
"stat",
144+
namespaceId);
145+
}
146+
147+
/*
148+
* Deal with the possibility that the statistics object already exists.
149+
*/
150+
if (SearchSysCacheExists2(STATEXTNAMENSP,
151+
CStringGetDatum(namestr),
152+
ObjectIdGetDatum(namespaceId)))
153+
{
154+
if (stmt->if_not_exists)
155+
{
156+
ereport(NOTICE,
157+
(errcode(ERRCODE_DUPLICATE_OBJECT),
158+
errmsg("statistics object \"%s\" already exists, skipping",
159+
namestr)));
160+
relation_close(rel, NoLock);
161+
return InvalidObjectAddress;
162+
}
163+
164+
ereport(ERROR,
165+
(errcode(ERRCODE_DUPLICATE_OBJECT),
166+
errmsg("statistics object \"%s\" already exists", namestr)));
167+
}
168+
151169
/*
152170
* Currently, we only allow simple column references in the expression
153171
* list. That will change someday, and again the grammar already supports
@@ -288,7 +306,7 @@ CreateStatistics(CreateStatsStmt *stmt)
288306
memset(values, 0, sizeof(values));
289307
memset(nulls, false, sizeof(nulls));
290308
values[Anum_pg_statistic_ext_stxrelid - 1] = ObjectIdGetDatum(relid);
291-
values[Anum_pg_statistic_ext_stxname - 1] = NameGetDatum(&stxname);
309+
values[Anum_pg_statistic_ext_stxname - 1] = CStringGetDatum(namestr);
292310
values[Anum_pg_statistic_ext_stxnamespace - 1] = ObjectIdGetDatum(namespaceId);
293311
values[Anum_pg_statistic_ext_stxowner - 1] = ObjectIdGetDatum(stxowner);
294312
values[Anum_pg_statistic_ext_stxkeys - 1] = PointerGetDatum(stxkeys);
@@ -405,3 +423,94 @@ UpdateStatisticsForTypeChange(Oid statsOid, Oid relationOid, int attnum,
405423
* Future types of extended stats will likely require us to work harder.
406424
*/
407425
}
426+
427+
/*
428+
* Select a nonconflicting name for a new statistics.
429+
*
430+
* name1, name2, and label are used the same way as for makeObjectName(),
431+
* except that the label can't be NULL; digits will be appended to the label
432+
* if needed to create a name that is unique within the specified namespace.
433+
*
434+
* Returns a palloc'd string.
435+
*
436+
* Note: it is theoretically possible to get a collision anyway, if someone
437+
* else chooses the same name concurrently. This is fairly unlikely to be
438+
* a problem in practice, especially if one is holding a share update
439+
* exclusive lock on the relation identified by name1. However, if choosing
440+
* multiple names within a single command, you'd better create the new object
441+
* and do CommandCounterIncrement before choosing the next one!
442+
*/
443+
static char *
444+
ChooseExtendedStatisticName(const char *name1, const char *name2,
445+
const char *label, Oid namespaceid)
446+
{
447+
int pass = 0;
448+
char *stxname = NULL;
449+
char modlabel[NAMEDATALEN];
450+
451+
/* try the unmodified label first */
452+
StrNCpy(modlabel, label, sizeof(modlabel));
453+
454+
for (;;)
455+
{
456+
Oid existingstats;
457+
458+
stxname = makeObjectName(name1, name2, modlabel);
459+
460+
existingstats = GetSysCacheOid2(STATEXTNAMENSP,
461+
PointerGetDatum(stxname),
462+
ObjectIdGetDatum(namespaceid));
463+
if (!OidIsValid(existingstats))
464+
break;
465+
466+
/* found a conflict, so try a new name component */
467+
pfree(stxname);
468+
snprintf(modlabel, sizeof(modlabel), "%s%d", label, ++pass);
469+
}
470+
471+
return stxname;
472+
}
473+
474+
/*
475+
* Generate "name2" for a new statistics given the list of column names for it
476+
* This will be passed to ChooseExtendedStatisticName along with the parent
477+
* table name and a suitable label.
478+
*
479+
* We know that less than NAMEDATALEN characters will actually be used,
480+
* so we can truncate the result once we've generated that many.
481+
*
482+
* XXX see also ChooseIndexNameAddition.
483+
*/
484+
static char *
485+
ChooseExtendedStatisticNameAddition(List *exprs)
486+
{
487+
char buf[NAMEDATALEN * 2];
488+
int buflen = 0;
489+
ListCell *lc;
490+
491+
buf[0] = '\0';
492+
foreach(lc, exprs)
493+
{
494+
ColumnRef *cref = (ColumnRef *) lfirst(lc);
495+
const char *name;
496+
497+
/* It should be one of these, but just skip if it happens not to be */
498+
if (!IsA(cref, ColumnRef))
499+
continue;
500+
501+
name = strVal((Value *) linitial(cref->fields));
502+
503+
if (buflen > 0)
504+
buf[buflen++] = '_'; /* insert _ between names */
505+
506+
/*
507+
* At this point we have buflen <= NAMEDATALEN. name should be less
508+
* than NAMEDATALEN already, but use strlcpy for paranoia.
509+
*/
510+
strlcpy(buf + buflen, name, NAMEDATALEN);
511+
buflen += strlen(buf + buflen);
512+
if (buflen >= NAMEDATALEN)
513+
break;
514+
}
515+
return pstrdup(buf);
516+
}

src/backend/parser/gram.y

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3489,12 +3489,13 @@ TableLikeOptionList:
34893489
;
34903490

34913491
TableLikeOption:
3492-
DEFAULTS { $$ = CREATE_TABLE_LIKE_DEFAULTS; }
3492+
COMMENTS { $$ = CREATE_TABLE_LIKE_COMMENTS; }
34933493
| CONSTRAINTS { $$ = CREATE_TABLE_LIKE_CONSTRAINTS; }
3494+
| DEFAULTS { $$ = CREATE_TABLE_LIKE_DEFAULTS; }
34943495
| IDENTITY_P { $$ = CREATE_TABLE_LIKE_IDENTITY; }
34953496
| INDEXES { $$ = CREATE_TABLE_LIKE_INDEXES; }
3497+
| STATISTICS { $$ = CREATE_TABLE_LIKE_STATISTICS; }
34963498
| STORAGE { $$ = CREATE_TABLE_LIKE_STORAGE; }
3497-
| COMMENTS { $$ = CREATE_TABLE_LIKE_COMMENTS; }
34983499
| ALL { $$ = CREATE_TABLE_LIKE_ALL; }
34993500
;
35003501

0 commit comments

Comments
 (0)