Skip to content

Commit edc3bcc

Browse files
committed
Fix broken handling of domains in atthasmissing logic.
If a domain type has a default, adding a column of that type (without any explicit DEFAULT clause) failed to install the domain's default value in existing rows, instead leaving the new column null. This is unexpected, and it used to work correctly before v11. The cause is confusion in the atthasmissing mechanism about which default value to install: we'd only consider installing an explicitly-specified default, and then we'd decide that no table rewrite is needed. To fix, take the responsibility for filling attmissingval out of StoreAttrDefault, and instead put it into ATExecAddColumn's existing logic that derives the correct value to fill the new column with. Also, centralize the logic that determines the need for default-related table rewriting there, instead of spreading it over four or five places. In the back branches, we'll leave the attmissingval-filling code in StoreAttrDefault even though it's now dead, for fear that some extension may be depending on that functionality to exist there. A separate HEAD-only patch will clean up the now-useless code. Reported-by: jian he <jian.universality@gmail.com> Author: jian he <jian.universality@gmail.com> Author: Tom Lane <tgl@sss.pgh.pa.us> Discussion: https://postgr.es/m/CACJufxHFssPvkP1we7WMhPD_1kwgbG52o=kQgL+TnVoX5LOyCQ@mail.gmail.com Backpatch-through: 13
1 parent 5c64ece commit edc3bcc

File tree

6 files changed

+233
-34
lines changed

6 files changed

+233
-34
lines changed

src/backend/catalog/heap.c

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
#include "pgstat.h"
6969
#include "storage/lmgr.h"
7070
#include "storage/predicate.h"
71+
#include "utils/array.h"
7172
#include "utils/builtins.h"
7273
#include "utils/fmgroids.h"
7374
#include "utils/inval.h"
@@ -2002,6 +2003,60 @@ RelationClearMissing(Relation rel)
20022003
table_close(attr_rel, RowExclusiveLock);
20032004
}
20042005

2006+
/*
2007+
* StoreAttrMissingVal
2008+
*
2009+
* Set the missing value of a single attribute.
2010+
*/
2011+
void
2012+
StoreAttrMissingVal(Relation rel, AttrNumber attnum, Datum missingval)
2013+
{
2014+
Datum valuesAtt[Natts_pg_attribute] = {0};
2015+
bool nullsAtt[Natts_pg_attribute] = {0};
2016+
bool replacesAtt[Natts_pg_attribute] = {0};
2017+
Relation attrrel;
2018+
Form_pg_attribute attStruct;
2019+
HeapTuple atttup,
2020+
newtup;
2021+
2022+
/* This is only supported for plain tables */
2023+
Assert(rel->rd_rel->relkind == RELKIND_RELATION);
2024+
2025+
/* Fetch the pg_attribute row */
2026+
attrrel = table_open(AttributeRelationId, RowExclusiveLock);
2027+
2028+
atttup = SearchSysCache2(ATTNUM,
2029+
ObjectIdGetDatum(RelationGetRelid(rel)),
2030+
Int16GetDatum(attnum));
2031+
if (!HeapTupleIsValid(atttup)) /* shouldn't happen */
2032+
elog(ERROR, "cache lookup failed for attribute %d of relation %u",
2033+
attnum, RelationGetRelid(rel));
2034+
attStruct = (Form_pg_attribute) GETSTRUCT(atttup);
2035+
2036+
/* Make a one-element array containing the value */
2037+
missingval = PointerGetDatum(construct_array(&missingval,
2038+
1,
2039+
attStruct->atttypid,
2040+
attStruct->attlen,
2041+
attStruct->attbyval,
2042+
attStruct->attalign));
2043+
2044+
/* Update the pg_attribute row */
2045+
valuesAtt[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(true);
2046+
replacesAtt[Anum_pg_attribute_atthasmissing - 1] = true;
2047+
2048+
valuesAtt[Anum_pg_attribute_attmissingval - 1] = missingval;
2049+
replacesAtt[Anum_pg_attribute_attmissingval - 1] = true;
2050+
2051+
newtup = heap_modify_tuple(atttup, RelationGetDescr(attrrel),
2052+
valuesAtt, nullsAtt, replacesAtt);
2053+
CatalogTupleUpdate(attrrel, &newtup->t_self, newtup);
2054+
2055+
/* clean up */
2056+
ReleaseSysCache(atttup);
2057+
table_close(attrrel, RowExclusiveLock);
2058+
}
2059+
20052060
/*
20062061
* SetAttrMissing
20072062
*
@@ -2329,13 +2384,8 @@ AddRelationNewConstraints(Relation rel,
23292384
castNode(Const, expr)->constisnull))
23302385
continue;
23312386

2332-
/* If the DEFAULT is volatile we cannot use a missing value */
2333-
if (colDef->missingMode &&
2334-
contain_volatile_functions_after_planning((Expr *) expr))
2335-
colDef->missingMode = false;
2336-
23372387
defOid = StoreAttrDefault(rel, colDef->attnum, expr, is_internal,
2338-
colDef->missingMode);
2388+
false);
23392389

23402390
cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
23412391
cooked->contype = CONSTR_DEFAULT;

src/backend/catalog/pg_attrdef.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,12 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
120120
valuesAtt[Anum_pg_attribute_atthasdef - 1] = true;
121121
replacesAtt[Anum_pg_attribute_atthasdef - 1] = true;
122122

123+
/*
124+
* Note: this code is dead so far as core Postgres is concerned,
125+
* because no caller passes add_column_mode = true anymore. We keep
126+
* it in back branches on the slight chance that some extension is
127+
* depending on it.
128+
*/
123129
if (rel->rd_rel->relkind == RELKIND_RELATION && add_column_mode &&
124130
!attgenerated)
125131
{

src/backend/commands/tablecmds.c

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7078,14 +7078,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
70787078
rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
70797079
rawEnt->attnum = attribute.attnum;
70807080
rawEnt->raw_default = copyObject(colDef->raw_default);
7081-
7082-
/*
7083-
* Attempt to skip a complete table rewrite by storing the specified
7084-
* DEFAULT value outside of the heap. This may be disabled inside
7085-
* AddRelationNewConstraints if the optimization cannot be applied.
7086-
*/
7087-
rawEnt->missingMode = (!colDef->generated);
7088-
7081+
rawEnt->missingMode = false; /* XXX vestigial */
70897082
rawEnt->generated = colDef->generated;
70907083

70917084
/*
@@ -7097,13 +7090,6 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
70977090

70987091
/* Make the additional catalog changes visible */
70997092
CommandCounterIncrement();
7100-
7101-
/*
7102-
* Did the request for a missing value work? If not we'll have to do a
7103-
* rewrite
7104-
*/
7105-
if (!rawEnt->missingMode)
7106-
tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
71077093
}
71087094

71097095
/*
@@ -7120,9 +7106,9 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
71207106
* rejects nulls. If there are any domain constraints then we construct
71217107
* an explicit NULL default value that will be passed through
71227108
* CoerceToDomain processing. (This is a tad inefficient, since it causes
7123-
* rewriting the table which we really don't have to do, but the present
7124-
* design of domain processing doesn't offer any simple way of checking
7125-
* the constraints more directly.)
7109+
* rewriting the table which we really wouldn't have to do; but we do it
7110+
* to preserve the historical behavior that such a failure will be raised
7111+
* only if the table currently contains some rows.)
71267112
*
71277113
* Note: we use build_column_default, and not just the cooked default
71287114
* returned by AddRelationNewConstraints, so that the right thing happens
@@ -7141,6 +7127,9 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
71417127
*/
71427128
if (RELKIND_HAS_STORAGE(relkind) && attribute.attnum > 0)
71437129
{
7130+
bool has_domain_constraints;
7131+
bool has_missing = false;
7132+
71447133
/*
71457134
* For an identity column, we can't use build_column_default(),
71467135
* because the sequence ownership isn't set yet. So do it manually.
@@ -7153,14 +7142,13 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
71537142
nve->typeId = typeOid;
71547143

71557144
defval = (Expr *) nve;
7156-
7157-
/* must do a rewrite for identity columns */
7158-
tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
71597145
}
71607146
else
71617147
defval = (Expr *) build_column_default(rel, attribute.attnum);
71627148

7163-
if (!defval && DomainHasConstraints(typeOid))
7149+
/* Build CoerceToDomain(NULL) expression if needed */
7150+
has_domain_constraints = DomainHasConstraints(typeOid);
7151+
if (!defval && has_domain_constraints)
71647152
{
71657153
Oid baseTypeId;
71667154
int32 baseTypeMod;
@@ -7186,18 +7174,61 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
71867174
{
71877175
NewColumnValue *newval;
71887176

7177+
/* Prepare defval for execution, either here or in Phase 3 */
7178+
defval = expression_planner(defval);
7179+
7180+
/* Add the new default to the newvals list */
71897181
newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
71907182
newval->attnum = attribute.attnum;
7191-
newval->expr = expression_planner(defval);
7183+
newval->expr = defval;
71927184
newval->is_generated = (colDef->generated != '\0');
71937185

71947186
tab->newvals = lappend(tab->newvals, newval);
7195-
}
71967187

7197-
if (DomainHasConstraints(typeOid))
7198-
tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
7188+
/*
7189+
* Attempt to skip a complete table rewrite by storing the
7190+
* specified DEFAULT value outside of the heap. This is only
7191+
* allowed for plain relations and non-generated columns, and the
7192+
* default expression can't be volatile (stable is OK). Note that
7193+
* contain_volatile_functions deems CoerceToDomain immutable, but
7194+
* here we consider that coercion to a domain with constraints is
7195+
* volatile; else it might fail even when the table is empty.
7196+
*/
7197+
if (rel->rd_rel->relkind == RELKIND_RELATION &&
7198+
!colDef->generated &&
7199+
!has_domain_constraints &&
7200+
!contain_volatile_functions((Node *) defval))
7201+
{
7202+
EState *estate;
7203+
ExprState *exprState;
7204+
Datum missingval;
7205+
bool missingIsNull;
7206+
7207+
/* Evaluate the default expression */
7208+
estate = CreateExecutorState();
7209+
exprState = ExecPrepareExpr(defval, estate);
7210+
missingval = ExecEvalExpr(exprState,
7211+
GetPerTupleExprContext(estate),
7212+
&missingIsNull);
7213+
/* If it turns out NULL, nothing to do; else store it */
7214+
if (!missingIsNull)
7215+
{
7216+
StoreAttrMissingVal(rel, attribute.attnum, missingval);
7217+
has_missing = true;
7218+
}
7219+
FreeExecutorState(estate);
7220+
}
7221+
else
7222+
{
7223+
/*
7224+
* Failed to use missing mode. We have to do a table rewrite
7225+
* to install the value.
7226+
*/
7227+
tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
7228+
}
7229+
}
71997230

7200-
if (!TupleDescAttr(rel->rd_att, attribute.attnum - 1)->atthasmissing)
7231+
if (!has_missing)
72017232
{
72027233
/*
72037234
* If the new column is NOT NULL, and there is no missing value,

src/include/catalog/heap.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ typedef struct RawColumnDefault
2828
{
2929
AttrNumber attnum; /* attribute to attach default to */
3030
Node *raw_default; /* default value (untransformed parse tree) */
31-
bool missingMode; /* true if part of add column processing */
31+
bool missingMode; /* obsolete, no longer used */
3232
char generated; /* attgenerated setting */
3333
} RawColumnDefault;
3434

@@ -115,6 +115,9 @@ extern List *AddRelationNewConstraints(Relation rel,
115115
const char *queryString);
116116

117117
extern void RelationClearMissing(Relation rel);
118+
119+
extern void StoreAttrMissingVal(Relation rel, AttrNumber attnum,
120+
Datum missingval);
118121
extern void SetAttrMissing(Oid relid, char *attname, char *value);
119122

120123
extern Node *cookDefault(ParseState *pstate,

src/test/regress/expected/fast_default.out

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,71 @@ SELECT comp();
245245
(1 row)
246246

247247
DROP TABLE T;
248+
-- Test domains with default value for table rewrite.
249+
CREATE DOMAIN domain1 AS int DEFAULT 11; -- constant
250+
CREATE DOMAIN domain2 AS int DEFAULT 10 + (random() * 10)::int; -- volatile
251+
CREATE DOMAIN domain3 AS text DEFAULT foo(4); -- stable
252+
CREATE DOMAIN domain4 AS text[]
253+
DEFAULT ('{"This", "is", "' || foo(4) || '","the", "real", "world"}')::TEXT[];
254+
CREATE TABLE t2 (a domain1);
255+
INSERT INTO t2 VALUES (1),(2);
256+
-- no table rewrite
257+
ALTER TABLE t2 ADD COLUMN b domain1 default 3;
258+
SELECT attnum, attname, atthasmissing, atthasdef, attmissingval
259+
FROM pg_attribute
260+
WHERE attnum > 0 AND attrelid = 't2'::regclass
261+
ORDER BY attnum;
262+
attnum | attname | atthasmissing | atthasdef | attmissingval
263+
--------+---------+---------------+-----------+---------------
264+
1 | a | f | f |
265+
2 | b | t | t | {3}
266+
(2 rows)
267+
268+
-- table rewrite should happen
269+
ALTER TABLE t2 ADD COLUMN c domain3 default left(random()::text,3);
270+
NOTICE: rewriting table t2 for reason 2
271+
-- no table rewrite
272+
ALTER TABLE t2 ADD COLUMN d domain4;
273+
SELECT attnum, attname, atthasmissing, atthasdef, attmissingval
274+
FROM pg_attribute
275+
WHERE attnum > 0 AND attrelid = 't2'::regclass
276+
ORDER BY attnum;
277+
attnum | attname | atthasmissing | atthasdef | attmissingval
278+
--------+---------+---------------+-----------+-----------------------------------
279+
1 | a | f | f |
280+
2 | b | f | t |
281+
3 | c | f | t |
282+
4 | d | t | f | {"{This,is,abcd,the,real,world}"}
283+
(4 rows)
284+
285+
-- table rewrite should happen
286+
ALTER TABLE t2 ADD COLUMN e domain2;
287+
NOTICE: rewriting table t2 for reason 2
288+
SELECT attnum, attname, atthasmissing, atthasdef, attmissingval
289+
FROM pg_attribute
290+
WHERE attnum > 0 AND attrelid = 't2'::regclass
291+
ORDER BY attnum;
292+
attnum | attname | atthasmissing | atthasdef | attmissingval
293+
--------+---------+---------------+-----------+---------------
294+
1 | a | f | f |
295+
2 | b | f | t |
296+
3 | c | f | t |
297+
4 | d | f | f |
298+
5 | e | f | f |
299+
(5 rows)
300+
301+
SELECT a, b, length(c) = 3 as c_ok, d, e >= 10 as e_ok FROM t2;
302+
a | b | c_ok | d | e_ok
303+
---+---+------+-------------------------------+------
304+
1 | 3 | t | {This,is,abcd,the,real,world} | t
305+
2 | 3 | t | {This,is,abcd,the,real,world} | t
306+
(2 rows)
307+
308+
DROP TABLE t2;
309+
DROP DOMAIN domain1;
310+
DROP DOMAIN domain2;
311+
DROP DOMAIN domain3;
312+
DROP DOMAIN domain4;
248313
DROP FUNCTION foo(INT);
249314
-- Fall back to full rewrite for volatile expressions
250315
CREATE TABLE T(pk INT NOT NULL PRIMARY KEY);

src/test/regress/sql/fast_default.sql

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,50 @@ SELECT comp();
237237

238238
DROP TABLE T;
239239

240+
-- Test domains with default value for table rewrite.
241+
CREATE DOMAIN domain1 AS int DEFAULT 11; -- constant
242+
CREATE DOMAIN domain2 AS int DEFAULT 10 + (random() * 10)::int; -- volatile
243+
CREATE DOMAIN domain3 AS text DEFAULT foo(4); -- stable
244+
CREATE DOMAIN domain4 AS text[]
245+
DEFAULT ('{"This", "is", "' || foo(4) || '","the", "real", "world"}')::TEXT[];
246+
247+
CREATE TABLE t2 (a domain1);
248+
INSERT INTO t2 VALUES (1),(2);
249+
250+
-- no table rewrite
251+
ALTER TABLE t2 ADD COLUMN b domain1 default 3;
252+
253+
SELECT attnum, attname, atthasmissing, atthasdef, attmissingval
254+
FROM pg_attribute
255+
WHERE attnum > 0 AND attrelid = 't2'::regclass
256+
ORDER BY attnum;
257+
258+
-- table rewrite should happen
259+
ALTER TABLE t2 ADD COLUMN c domain3 default left(random()::text,3);
260+
261+
-- no table rewrite
262+
ALTER TABLE t2 ADD COLUMN d domain4;
263+
264+
SELECT attnum, attname, atthasmissing, atthasdef, attmissingval
265+
FROM pg_attribute
266+
WHERE attnum > 0 AND attrelid = 't2'::regclass
267+
ORDER BY attnum;
268+
269+
-- table rewrite should happen
270+
ALTER TABLE t2 ADD COLUMN e domain2;
271+
272+
SELECT attnum, attname, atthasmissing, atthasdef, attmissingval
273+
FROM pg_attribute
274+
WHERE attnum > 0 AND attrelid = 't2'::regclass
275+
ORDER BY attnum;
276+
277+
SELECT a, b, length(c) = 3 as c_ok, d, e >= 10 as e_ok FROM t2;
278+
279+
DROP TABLE t2;
280+
DROP DOMAIN domain1;
281+
DROP DOMAIN domain2;
282+
DROP DOMAIN domain3;
283+
DROP DOMAIN domain4;
240284
DROP FUNCTION foo(INT);
241285

242286
-- Fall back to full rewrite for volatile expressions

0 commit comments

Comments
 (0)