Skip to content

Commit 1015162

Browse files
committed
Fix handling of extended expression statistics in CREATE TABLE LIKE.
transformTableLikeClause believed that it could process extended statistics immediately because "the representation of CreateStatsStmt doesn't depend on column numbers". That was true when extended stats were first introduced, but it was falsified by the addition of extended stats on expressions: the parsed expression tree is fed forward by the LIKE option, and that will contain Vars. So if the new table doesn't have attnums identical to the old one's (typically because there are some dropped columns in the old one), that doesn't work. The CREATE goes through, but it emits invalid statistics objects that will cause problems later. Fortunately, we already have logic that can adapt expression trees to the possibly-new column numbering. To use it, we have to delay processing of CREATE_TABLE_LIKE_STATISTICS into expandTableLikeClause, just as for other LIKE options that involve expressions. Per bug #18468 from Alexander Lakhin. Back-patch to v14 where extended statistics on expressions were added. Discussion: https://postgr.es/m/18468-f5add190e3fa5902@postgresql.org
1 parent 5ac3406 commit 1015162

File tree

3 files changed

+86
-73
lines changed

3 files changed

+86
-73
lines changed

src/backend/parser/parse_utilcmd.c

Lines changed: 63 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ typedef struct
8484
List *fkconstraints; /* FOREIGN KEY constraints */
8585
List *ixconstraints; /* index-creating constraints */
8686
List *likeclauses; /* LIKE clauses that need post-processing */
87-
List *extstats; /* cloned extended statistics */
8887
List *blist; /* "before list" of things to do before
8988
* creating the table */
9089
List *alist; /* "after list" of things to do after creating
@@ -117,13 +116,14 @@ static void transformTableLikeClause(CreateStmtContext *cxt,
117116
static void transformOfType(CreateStmtContext *cxt,
118117
TypeName *ofTypename);
119118
static CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel,
120-
Oid heapRelid, Oid source_statsid);
119+
Oid heapRelid,
120+
Oid source_statsid,
121+
const AttrMap *attmap);
121122
static List *get_collation(Oid collation, Oid actual_datatype);
122123
static List *get_opclass(Oid opclass, Oid actual_datatype);
123124
static void transformIndexConstraints(CreateStmtContext *cxt);
124125
static IndexStmt *transformIndexConstraint(Constraint *constraint,
125126
CreateStmtContext *cxt);
126-
static void transformExtendedStatistics(CreateStmtContext *cxt);
127127
static void transformFKConstraints(CreateStmtContext *cxt,
128128
bool skipValidation,
129129
bool isAddConstraint);
@@ -243,7 +243,6 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
243243
cxt.fkconstraints = NIL;
244244
cxt.ixconstraints = NIL;
245245
cxt.likeclauses = NIL;
246-
cxt.extstats = NIL;
247246
cxt.blist = NIL;
248247
cxt.alist = NIL;
249248
cxt.pkey = NULL;
@@ -336,11 +335,6 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
336335
*/
337336
transformCheckConstraints(&cxt, !cxt.isforeign);
338337

339-
/*
340-
* Postprocess extended statistics.
341-
*/
342-
transformExtendedStatistics(&cxt);
343-
344338
/*
345339
* Output results.
346340
*/
@@ -1123,61 +1117,25 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
11231117
}
11241118

11251119
/*
1126-
* We cannot yet deal with defaults, CHECK constraints, or indexes, since
1127-
* we don't yet know what column numbers the copied columns will have in
1128-
* the finished table. If any of those options are specified, add the
1129-
* LIKE clause to cxt->likeclauses so that expandTableLikeClause will be
1130-
* called after we do know that. Also, remember the relation OID so that
1131-
* expandTableLikeClause is certain to open the same table.
1120+
* We cannot yet deal with defaults, CHECK constraints, indexes, or
1121+
* statistics, since we don't yet know what column numbers the copied
1122+
* columns will have in the finished table. If any of those options are
1123+
* specified, add the LIKE clause to cxt->likeclauses so that
1124+
* expandTableLikeClause will be called after we do know that. Also,
1125+
* remember the relation OID so that expandTableLikeClause is certain to
1126+
* open the same table.
11321127
*/
11331128
if (table_like_clause->options &
11341129
(CREATE_TABLE_LIKE_DEFAULTS |
11351130
CREATE_TABLE_LIKE_GENERATED |
11361131
CREATE_TABLE_LIKE_CONSTRAINTS |
1137-
CREATE_TABLE_LIKE_INDEXES))
1132+
CREATE_TABLE_LIKE_INDEXES |
1133+
CREATE_TABLE_LIKE_STATISTICS))
11381134
{
11391135
table_like_clause->relationOid = RelationGetRelid(relation);
11401136
cxt->likeclauses = lappend(cxt->likeclauses, table_like_clause);
11411137
}
11421138

1143-
/*
1144-
* We may copy extended statistics if requested, since the representation
1145-
* of CreateStatsStmt doesn't depend on column numbers.
1146-
*/
1147-
if (table_like_clause->options & CREATE_TABLE_LIKE_STATISTICS)
1148-
{
1149-
List *parent_extstats;
1150-
ListCell *l;
1151-
1152-
parent_extstats = RelationGetStatExtList(relation);
1153-
1154-
foreach(l, parent_extstats)
1155-
{
1156-
Oid parent_stat_oid = lfirst_oid(l);
1157-
CreateStatsStmt *stats_stmt;
1158-
1159-
stats_stmt = generateClonedExtStatsStmt(cxt->relation,
1160-
RelationGetRelid(relation),
1161-
parent_stat_oid);
1162-
1163-
/* Copy comment on statistics object, if requested */
1164-
if (table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS)
1165-
{
1166-
comment = GetComment(parent_stat_oid, StatisticExtRelationId, 0);
1167-
1168-
/*
1169-
* We make use of CreateStatsStmt's stxcomment option, so as
1170-
* not to need to know now what name the statistics will have.
1171-
*/
1172-
stats_stmt->stxcomment = comment;
1173-
}
1174-
1175-
cxt->extstats = lappend(cxt->extstats, stats_stmt);
1176-
}
1177-
1178-
list_free(parent_extstats);
1179-
}
1180-
11811139
/*
11821140
* Close the parent rel, but keep our AccessShareLock on it until xact
11831141
* commit. That will prevent someone else from deleting or ALTERing the
@@ -1443,6 +1401,44 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
14431401
}
14441402
}
14451403

1404+
/*
1405+
* Process extended statistics if required.
1406+
*/
1407+
if (table_like_clause->options & CREATE_TABLE_LIKE_STATISTICS)
1408+
{
1409+
List *parent_extstats;
1410+
ListCell *l;
1411+
1412+
parent_extstats = RelationGetStatExtList(relation);
1413+
1414+
foreach(l, parent_extstats)
1415+
{
1416+
Oid parent_stat_oid = lfirst_oid(l);
1417+
CreateStatsStmt *stats_stmt;
1418+
1419+
stats_stmt = generateClonedExtStatsStmt(heapRel,
1420+
RelationGetRelid(childrel),
1421+
parent_stat_oid,
1422+
attmap);
1423+
1424+
/* Copy comment on statistics object, if requested */
1425+
if (table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS)
1426+
{
1427+
comment = GetComment(parent_stat_oid, StatisticExtRelationId, 0);
1428+
1429+
/*
1430+
* We make use of CreateStatsStmt's stxcomment option, so as
1431+
* not to need to know now what name the statistics will have.
1432+
*/
1433+
stats_stmt->stxcomment = comment;
1434+
}
1435+
1436+
result = lappend(result, stats_stmt);
1437+
}
1438+
1439+
list_free(parent_extstats);
1440+
}
1441+
14461442
/* Done with child rel */
14471443
table_close(childrel, NoLock);
14481444

@@ -1876,10 +1872,12 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx,
18761872
* Generate a CreateStatsStmt node using information from an already existing
18771873
* extended statistic "source_statsid", for the rel identified by heapRel and
18781874
* heapRelid.
1875+
*
1876+
* Attribute numbers in expression Vars are adjusted according to attmap.
18791877
*/
18801878
static CreateStatsStmt *
18811879
generateClonedExtStatsStmt(RangeVar *heapRel, Oid heapRelid,
1882-
Oid source_statsid)
1880+
Oid source_statsid, const AttrMap *attmap)
18831881
{
18841882
HeapTuple ht_stats;
18851883
Form_pg_statistic_ext statsrec;
@@ -1963,10 +1961,19 @@ generateClonedExtStatsStmt(RangeVar *heapRel, Oid heapRelid,
19631961

19641962
foreach(lc, exprs)
19651963
{
1964+
Node *expr = (Node *) lfirst(lc);
19661965
StatsElem *selem = makeNode(StatsElem);
1966+
bool found_whole_row;
1967+
1968+
/* Adjust Vars to match new table's column numbering */
1969+
expr = map_variable_attnos(expr,
1970+
1, 0,
1971+
attmap,
1972+
InvalidOid,
1973+
&found_whole_row);
19671974

19681975
selem->name = NULL;
1969-
selem->expr = (Node *) lfirst(lc);
1976+
selem->expr = expr;
19701977

19711978
def_names = lappend(def_names, selem);
19721979
}
@@ -2691,19 +2698,6 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
26912698
return index;
26922699
}
26932700

2694-
/*
2695-
* transformExtendedStatistics
2696-
* Handle extended statistic objects
2697-
*
2698-
* Right now, there's nothing to do here, so we just append the list to
2699-
* the existing "after" list.
2700-
*/
2701-
static void
2702-
transformExtendedStatistics(CreateStmtContext *cxt)
2703-
{
2704-
cxt->alist = list_concat(cxt->alist, cxt->extstats);
2705-
}
2706-
27072701
/*
27082702
* transformCheckConstraints
27092703
* handle CHECK constraints
@@ -3346,7 +3340,6 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
33463340
cxt.fkconstraints = NIL;
33473341
cxt.ixconstraints = NIL;
33483342
cxt.likeclauses = NIL;
3349-
cxt.extstats = NIL;
33503343
cxt.blist = NIL;
33513344
cxt.alist = NIL;
33523345
cxt.pkey = NULL;
@@ -3628,9 +3621,6 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
36283621
newcmds = lappend(newcmds, newcmd);
36293622
}
36303623

3631-
/* Append extended statistics objects */
3632-
transformExtendedStatistics(&cxt);
3633-
36343624
/* Close rel */
36353625
relation_close(rel, NoLock);
36363626

src/test/regress/expected/create_table_like.out

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,23 @@ Check constraints:
261261
Inherits: test_like_5,
262262
test_like_5x
263263

264+
-- Test updating of column numbers in statistics expressions (bug #18468)
265+
CREATE TABLE test_like_6 (a int, c text, b text);
266+
CREATE STATISTICS ext_stat ON (a || b) FROM test_like_6;
267+
ALTER TABLE test_like_6 DROP COLUMN c;
268+
CREATE TABLE test_like_6c (LIKE test_like_6 INCLUDING ALL);
269+
\d+ test_like_6c
270+
Table "public.test_like_6c"
271+
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
272+
--------+---------+-----------+----------+---------+----------+--------------+-------------
273+
a | integer | | | | plain | |
274+
b | text | | | | extended | |
275+
Statistics objects:
276+
"public.test_like_6c_expr_stat" ON (a || b) FROM test_like_6c
277+
264278
DROP TABLE test_like_4, test_like_4a, test_like_4b, test_like_4c, test_like_4d;
265279
DROP TABLE test_like_5, test_like_5x, test_like_5c;
280+
DROP TABLE test_like_6, test_like_6c;
266281
CREATE TABLE inhg (x text, LIKE inhx INCLUDING INDEXES, y text); /* copies indexes */
267282
INSERT INTO inhg VALUES (5, 10);
268283
INSERT INTO inhg VALUES (20, 10); -- should fail

src/test/regress/sql/create_table_like.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,16 @@ CREATE TABLE test_like_5c (LIKE test_like_4 INCLUDING ALL)
9595
INHERITS (test_like_5, test_like_5x);
9696
\d test_like_5c
9797

98+
-- Test updating of column numbers in statistics expressions (bug #18468)
99+
CREATE TABLE test_like_6 (a int, c text, b text);
100+
CREATE STATISTICS ext_stat ON (a || b) FROM test_like_6;
101+
ALTER TABLE test_like_6 DROP COLUMN c;
102+
CREATE TABLE test_like_6c (LIKE test_like_6 INCLUDING ALL);
103+
\d+ test_like_6c
104+
98105
DROP TABLE test_like_4, test_like_4a, test_like_4b, test_like_4c, test_like_4d;
99106
DROP TABLE test_like_5, test_like_5x, test_like_5c;
107+
DROP TABLE test_like_6, test_like_6c;
100108

101109
CREATE TABLE inhg (x text, LIKE inhx INCLUDING INDEXES, y text); /* copies indexes */
102110
INSERT INTO inhg VALUES (5, 10);

0 commit comments

Comments
 (0)