Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 3d0e03a

Browse files
author
Commitfest Bot
committed
[CF 5641] v5 - support fast default for domain with constraints
This branch was automatically generated by a robot using patches from an email thread registered at: https://commitfest.postgresql.org/patch/5641 The branch will be overwritten each time a new patch version is posted to the thread, and also periodically to check for bitrot caused by changes on the master branch. Patch(es): https://www.postgresql.org/message-id/CACJufxGNCw51aeVxXOtgzgbE9J710e91umNzH-56rG5tmtK8mw@mail.gmail.com Author(s): Jian He
2 parents 4c08ecd + c2c4244 commit 3d0e03a

File tree

7 files changed

+234
-16
lines changed

7 files changed

+234
-16
lines changed

src/backend/commands/tablecmds.c

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7429,15 +7429,6 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
74297429
* NULL if so, so without any modification of the tuple data we will get
74307430
* the effect of NULL values in the new column.
74317431
*
7432-
* An exception occurs when the new column is of a domain type: the domain
7433-
* might have a not-null constraint, or a check constraint that indirectly
7434-
* rejects nulls. If there are any domain constraints then we construct
7435-
* an explicit NULL default value that will be passed through
7436-
* CoerceToDomain processing. (This is a tad inefficient, since it causes
7437-
* rewriting the table which we really wouldn't have to do; but we do it
7438-
* to preserve the historical behavior that such a failure will be raised
7439-
* only if the table currently contains some rows.)
7440-
*
74417432
* Note: we use build_column_default, and not just the cooked default
74427433
* returned by AddRelationNewConstraints, so that the right thing happens
74437434
* when a datatype's default applies.
@@ -7456,6 +7447,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
74567447
{
74577448
bool has_domain_constraints;
74587449
bool has_missing = false;
7450+
bool has_volatile = false;
74597451

74607452
/*
74617453
* For an identity column, we can't use build_column_default(),
@@ -7473,8 +7465,17 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
74737465
else
74747466
defval = (Expr *) build_column_default(rel, attribute->attnum);
74757467

7468+
has_domain_constraints = DomainHaveVolatileConstraints(attribute->atttypid, &has_volatile);
7469+
/*
7470+
* Adding column with volatile domain constraint requires table rewrite
7471+
*/
7472+
if (has_volatile)
7473+
{
7474+
Assert(has_domain_constraints);
7475+
tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
7476+
}
7477+
74767478
/* Build CoerceToDomain(NULL) expression if needed */
7477-
has_domain_constraints = DomainHasConstraints(attribute->atttypid);
74787479
if (!defval && has_domain_constraints)
74797480
{
74807481
Oid baseTypeId;
@@ -7516,14 +7517,18 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
75167517
* Attempt to skip a complete table rewrite by storing the
75177518
* specified DEFAULT value outside of the heap. This is only
75187519
* allowed for plain relations and non-generated columns, and the
7519-
* default expression can't be volatile (stable is OK). Note that
7520-
* contain_volatile_functions deems CoerceToDomain immutable, but
7521-
* here we consider that coercion to a domain with constraints is
7522-
* volatile; else it might fail even when the table is empty.
7520+
* default expression can't be volatile (stable is OK), and the
7521+
* domain constraint can't be volatile (stable is OK).
7522+
*
7523+
* Note that contain_volatile_functions deems CoerceToDomain
7524+
* immutable. However we have computed CoerceToDomain is volatile
7525+
* or not via DomainHaveVolatileConstraints. We use soft error
7526+
* evaluation of CoerceToDomain, if evaluation failed, then set
7527+
* table rewrite to true.
75237528
*/
75247529
if (rel->rd_rel->relkind == RELKIND_RELATION &&
75257530
!colDef->generated &&
7526-
!has_domain_constraints &&
7531+
!has_volatile &&
75277532
!contain_volatile_functions((Node *) defval))
75287533
{
75297534
EState *estate;
@@ -7533,10 +7538,18 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
75337538

75347539
/* Evaluate the default expression */
75357540
estate = CreateExecutorState();
7536-
exprState = ExecPrepareExpr(defval, estate);
7541+
exprState = ExecPrepareExprSafe(defval, estate);
7542+
75377543
missingval = ExecEvalExpr(exprState,
75387544
GetPerTupleExprContext(estate),
75397545
&missingIsNull);
7546+
7547+
if (SOFT_ERROR_OCCURRED(exprState->escontext))
7548+
{
7549+
missingIsNull = true;
7550+
tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
7551+
}
7552+
75407553
/* If it turns out NULL, nothing to do; else store it */
75417554
if (!missingIsNull)
75427555
{

src/backend/executor/execExpr.c

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,47 @@ ExecInitExpr(Expr *node, PlanState *parent)
170170
return state;
171171
}
172172

173+
/*
174+
* ExecInitExprSafe: soft error variant of ExecInitExpr.
175+
*
176+
* use it only for expression nodes support soft errors, not all expression
177+
* nodes support it.
178+
*/
179+
ExprState *
180+
ExecInitExprSafe(Expr *node, PlanState *parent)
181+
{
182+
ExprState *state;
183+
ExprEvalStep scratch = {0};
184+
185+
/* Special case: NULL expression produces a NULL ExprState pointer */
186+
if (node == NULL)
187+
return NULL;
188+
189+
/* Initialize ExprState with empty step list */
190+
state = makeNode(ExprState);
191+
state->expr = node;
192+
state->parent = parent;
193+
state->ext_params = NULL;
194+
state->escontext = makeNode(ErrorSaveContext);
195+
state->escontext->type = T_ErrorSaveContext;
196+
state->escontext->error_occurred = false;
197+
state->escontext->details_wanted = true;
198+
199+
/* Insert setup steps as needed */
200+
ExecCreateExprSetupSteps(state, (Node *) node);
201+
202+
/* Compile the expression proper */
203+
ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
204+
205+
/* Finally, append a DONE step */
206+
scratch.opcode = EEOP_DONE_RETURN;
207+
ExprEvalPushStep(state, &scratch);
208+
209+
ExecReadyExpr(state);
210+
211+
return state;
212+
}
213+
173214
/*
174215
* ExecInitExprWithParams: prepare a standalone expression tree for execution
175216
*
@@ -778,6 +819,28 @@ ExecPrepareExpr(Expr *node, EState *estate)
778819
return result;
779820
}
780821

822+
/*
823+
* ExecPrepareExprSafe: soft error variant of ExecPrepareExpr.
824+
*
825+
* use it when expression node *support* soft error expression execution.
826+
*/
827+
ExprState *
828+
ExecPrepareExprSafe(Expr *node, EState *estate)
829+
{
830+
ExprState *result;
831+
MemoryContext oldcontext;
832+
833+
oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
834+
835+
node = expression_planner(node);
836+
837+
result = ExecInitExprSafe(node, NULL);
838+
839+
MemoryContextSwitchTo(oldcontext);
840+
841+
return result;
842+
}
843+
781844
/*
782845
* ExecPrepareQual --- initialize for qual execution outside a normal
783846
* Plan tree context.

src/backend/utils/cache/typcache.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1500,6 +1500,46 @@ DomainHasConstraints(Oid type_id)
15001500
}
15011501

15021502

1503+
/*
1504+
* Check whether a domain has any constraints, and determine if any of those
1505+
* constraints contain volatile expressions.
1506+
*
1507+
* To detect volatile expressions within domain check constraints, ensure that
1508+
* have_volatile is not NULL. If have_volatile is NULL, the behavior is
1509+
* equivalent to that of DomainHasConstraints.
1510+
*/
1511+
bool
1512+
DomainHaveVolatileConstraints(Oid type_id, bool *have_volatile)
1513+
{
1514+
TypeCacheEntry *typentry;
1515+
1516+
/*
1517+
* Note: a side effect is to cause the typcache's domain data to become
1518+
* valid. This is fine since we'll likely need it soon if there is any.
1519+
*/
1520+
typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
1521+
1522+
if (typentry->domainData != NULL)
1523+
{
1524+
ListCell *lc;
1525+
1526+
foreach(lc, typentry->domainData->constraints)
1527+
{
1528+
DomainConstraintState *r = (DomainConstraintState *) lfirst(lc);
1529+
1530+
if (r->constrainttype == DOM_CONSTRAINT_CHECK &&
1531+
contain_volatile_functions((Node *) r->check_expr))
1532+
{
1533+
if (have_volatile)
1534+
*have_volatile = true;
1535+
break;
1536+
}
1537+
}
1538+
return true;
1539+
}
1540+
return false;
1541+
}
1542+
15031543
/*
15041544
* array_element_has_equality and friends are helper routines to check
15051545
* whether we should believe that array_eq and related functions will work

src/include/executor/executor.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ ExecProcNode(PlanState *node)
318318
* prototypes from functions in execExpr.c
319319
*/
320320
extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
321+
extern ExprState *ExecInitExprSafe(Expr *node, PlanState *parent);
321322
extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params);
322323
extern ExprState *ExecInitQual(List *qual, PlanState *parent);
323324
extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
@@ -366,6 +367,7 @@ extern ProjectionInfo *ExecBuildUpdateProjection(List *targetList,
366367
TupleTableSlot *slot,
367368
PlanState *parent);
368369
extern ExprState *ExecPrepareExpr(Expr *node, EState *estate);
370+
extern ExprState *ExecPrepareExprSafe(Expr *node, EState *estate);
369371
extern ExprState *ExecPrepareQual(List *qual, EState *estate);
370372
extern ExprState *ExecPrepareCheck(List *qual, EState *estate);
371373
extern List *ExecPrepareExprList(List *nodes, EState *estate);

src/include/utils/typcache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ extern void InitDomainConstraintRef(Oid type_id, DomainConstraintRef *ref,
184184
extern void UpdateDomainConstraintRef(DomainConstraintRef *ref);
185185

186186
extern bool DomainHasConstraints(Oid type_id);
187+
extern bool DomainHaveVolatileConstraints(Oid type_id, bool *have_volatile);
187188

188189
extern TupleDesc lookup_rowtype_tupdesc(Oid type_id, int32 typmod);
189190

src/test/regress/expected/fast_default.out

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,11 +317,66 @@ SELECT a, b, length(c) = 3 as c_ok, d, e >= 10 as e_ok FROM t2;
317317
2 | 3 | t | {This,is,abcd,the,real,world} | t
318318
(2 rows)
319319

320+
---test fast default over domains with constraints
321+
CREATE DOMAIN domain5 AS int check(value > 10) default 8;
322+
CREATE DOMAIN domain6 as int not null;
323+
CREATE DOMAIN domain7 as int check(value > 10) DEFAULT random(min=>10, max=>100);
324+
CREATE DOMAIN domain8 as int check((value + random(min=>11::int, max=>11)) > 12);
325+
--tests with empty table.
326+
CREATE TABLE t3(a int);
327+
ALTER TABLE t3 ADD COLUMN b domain5 default 1; --ok, table rewrite
328+
NOTICE: rewriting table t3 for reason 2
329+
ALTER TABLE t3 ADD COLUMN c domain6 default 11 + NULL; --ok, table rewrite
330+
NOTICE: rewriting table t3 for reason 2
331+
ALTER TABLE t3 ADD COLUMN d domain7 default 2; --ok, table rewrite
332+
NOTICE: rewriting table t3 for reason 2
333+
ALTER TABLE t3 ADD COLUMN e domain8 default 3; --ok, table rewrite
334+
NOTICE: rewriting table t3 for reason 2
335+
DROP TABLE t3;
336+
--tests with non-empty table.
337+
CREATE TABLE t3(a int);
338+
INSERT INTO t3 VALUES(1),(2);
339+
ALTER TABLE t3 ADD COLUMN b domain5; --table rewrite, then fail
340+
NOTICE: rewriting table t3 for reason 2
341+
ERROR: value for domain domain5 violates check constraint "domain5_check"
342+
ALTER TABLE t3 ADD COLUMN b domain6; --table rewrite, then fail
343+
NOTICE: rewriting table t3 for reason 2
344+
ERROR: domain domain6 does not allow null values
345+
ALTER TABLE t3 ADD COLUMN b domain5 default 12; --no table rewrite
346+
ALTER TABLE t3 ADD COLUMN c domain6 default 13; --no table rewrite
347+
--no table rewrite. explicit column default expression override domain default
348+
--expression
349+
ALTER TABLE t3 ADD COLUMN d domain7 default 15;
350+
SELECT attnum, attname, atthasmissing, atthasdef, attmissingval
351+
FROM pg_attribute
352+
WHERE attnum > 0 AND attrelid = 't3'::regclass AND attisdropped is false
353+
AND atthasmissing
354+
ORDER BY attnum;
355+
attnum | attname | atthasmissing | atthasdef | attmissingval
356+
--------+---------+---------------+-----------+---------------
357+
2 | b | t | t | {12}
358+
3 | c | t | t | {13}
359+
4 | d | t | t | {15}
360+
(3 rows)
361+
362+
--table rewrite. we are applying domain volatile default expresion
363+
ALTER TABLE t3 ADD COLUMN e domain7;
364+
NOTICE: rewriting table t3 for reason 2
365+
--table rewrite for volatile domain constraints.
366+
ALTER TABLE t3 ADD COLUMN f domain8 default 14; --table rewrite
367+
NOTICE: rewriting table t3 for reason 2
368+
ALTER TABLE t3 ADD COLUMN f1 domain8; --table rewrite
369+
NOTICE: rewriting table t3 for reason 2
320370
DROP TABLE t2;
371+
DROP TABLE t3;
321372
DROP DOMAIN domain1;
322373
DROP DOMAIN domain2;
323374
DROP DOMAIN domain3;
324375
DROP DOMAIN domain4;
376+
DROP DOMAIN domain5;
377+
DROP DOMAIN domain6;
378+
DROP DOMAIN domain7;
379+
DROP DOMAIN domain8;
325380
DROP FUNCTION foo(INT);
326381
-- Fall back to full rewrite for volatile expressions
327382
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
@@ -287,11 +287,55 @@ ORDER BY attnum;
287287

288288
SELECT a, b, length(c) = 3 as c_ok, d, e >= 10 as e_ok FROM t2;
289289

290+
---test fast default over domains with constraints
291+
CREATE DOMAIN domain5 AS int check(value > 10) default 8;
292+
CREATE DOMAIN domain6 as int not null;
293+
CREATE DOMAIN domain7 as int check(value > 10) DEFAULT random(min=>10, max=>100);
294+
CREATE DOMAIN domain8 as int check((value + random(min=>11::int, max=>11)) > 12);
295+
296+
--tests with empty table.
297+
CREATE TABLE t3(a int);
298+
ALTER TABLE t3 ADD COLUMN b domain5 default 1; --ok, table rewrite
299+
ALTER TABLE t3 ADD COLUMN c domain6 default 11 + NULL; --ok, table rewrite
300+
ALTER TABLE t3 ADD COLUMN d domain7 default 2; --ok, table rewrite
301+
ALTER TABLE t3 ADD COLUMN e domain8 default 3; --ok, table rewrite
302+
DROP TABLE t3;
303+
304+
--tests with non-empty table.
305+
CREATE TABLE t3(a int);
306+
INSERT INTO t3 VALUES(1),(2);
307+
308+
ALTER TABLE t3 ADD COLUMN b domain5; --table rewrite, then fail
309+
ALTER TABLE t3 ADD COLUMN b domain6; --table rewrite, then fail
310+
ALTER TABLE t3 ADD COLUMN b domain5 default 12; --no table rewrite
311+
ALTER TABLE t3 ADD COLUMN c domain6 default 13; --no table rewrite
312+
--no table rewrite. explicit column default expression override domain default
313+
--expression
314+
ALTER TABLE t3 ADD COLUMN d domain7 default 15;
315+
316+
SELECT attnum, attname, atthasmissing, atthasdef, attmissingval
317+
FROM pg_attribute
318+
WHERE attnum > 0 AND attrelid = 't3'::regclass AND attisdropped is false
319+
AND atthasmissing
320+
ORDER BY attnum;
321+
322+
--table rewrite. we are applying domain volatile default expresion
323+
ALTER TABLE t3 ADD COLUMN e domain7;
324+
325+
--table rewrite for volatile domain constraints.
326+
ALTER TABLE t3 ADD COLUMN f domain8 default 14; --table rewrite
327+
ALTER TABLE t3 ADD COLUMN f1 domain8; --table rewrite
328+
290329
DROP TABLE t2;
330+
DROP TABLE t3;
291331
DROP DOMAIN domain1;
292332
DROP DOMAIN domain2;
293333
DROP DOMAIN domain3;
294334
DROP DOMAIN domain4;
335+
DROP DOMAIN domain5;
336+
DROP DOMAIN domain6;
337+
DROP DOMAIN domain7;
338+
DROP DOMAIN domain8;
295339
DROP FUNCTION foo(INT);
296340

297341
-- Fall back to full rewrite for volatile expressions

0 commit comments

Comments
 (0)