hi. in previous patches v6-0001 to v6-0003, we added support for ALTER TABLE ADD COLUMN with fast defaults for domains having non-volatile constraints.
inspired by another patch of mine: https://commitfest.postgresql.org/patch/5907 I believe it's doable to perform only a table scan when using ALTER TABLE ADD COLUMN with a domain that has volatile constraints. some example: CREATE DOMAIN domain8 as int check((value + random(min=>11::int, max=>11)) > 12); CREATE TABLE t3(a int); INSERT INTO t3 VALUES(1),(2); ALTER TABLE t3 ADD COLUMN f domain8 default 1; --error while coercing to domain ALTER TABLE t3 ADD COLUMN f domain8 default 20; --ok The idea is the same as mentioned in [1], for struct NewColumnValue, add another field (scan_only) to indicate that we use table scan to evaluate the CoerceToDomain node. summary of the attached v7. v7-0001, v7-00002: preparatory patch. v7-0003 adds fast default support for ALTER TABLE ADD COLUMN when the domain has non-volatile constraints. A table rewrite is still required for domains with volatile constraints. v7-0004 skip table rewrite (table scan only) for ALTER TABLE ADD COLUMN with domains has volatile constraints. [1] https://postgr.es/m/cacjufxfhwywzf2sjs9txskeya8hstxgdb8q2qwwwbo5q1sm...@mail.gmail.com
From 78cf6b0d9013267ad7d8e2e9a1ac2fac564df077 Mon Sep 17 00:00:00 2001 From: jian he <jian.universal...@gmail.com> Date: Mon, 2 Jun 2025 16:50:34 +0800 Subject: [PATCH v7 3/4] fast default for domain with constraints This is primarily done by evaluating CoerceToDomain with soft error support. If CoerceToDomain is evaluated as false in ATExecAddColumn, the defval node's value cannot be cast to the domain type. However, in some cases like when the table is empty, we cannot explicitly error out in ATExecAddColumn (Phase 2). For example, imagine add a new domain column to empty x, and the column domain specification is ``CHECK(value > 10) DEFAULT 8``. In such situations, the ALTER TABLE ADD COLUMN should be success. Thanks to commit aaaf9449ec6be62cb0d30ed3588dc384f56274bf[1], ExprState.escontext (ErrorSaveContext) was added, and ExecEvalConstraintNotNull, ExecEvalConstraintCheck were changed to use errsave instead of hard error. Now we can evaluate CoerceToDomain in a soft error way. However we do table rewrite for domain with volatile check constraints. discussion: https://postgr.es/m/cacjufxe_+izbr1i49k_ahigpppwltji6km8nosc7fwvkdem...@mail.gmail.com [1]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274bf --- src/backend/commands/tablecmds.c | 46 ++++++++++++++-------- src/test/regress/expected/fast_default.out | 44 +++++++++++++++++++++ src/test/regress/sql/fast_default.sql | 37 +++++++++++++++++ 3 files changed, 111 insertions(+), 16 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..c58094a39c0 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -7428,15 +7428,6 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, * NULL if so, so without any modification of the tuple data we will get * the effect of NULL values in the new column. * - * An exception occurs when the new column is of a domain type: the domain - * might have a not-null constraint, or a check constraint that indirectly - * rejects nulls. If there are any domain constraints then we construct - * an explicit NULL default value that will be passed through - * CoerceToDomain processing. (This is a tad inefficient, since it causes - * rewriting the table which we really wouldn't have to do; but we do it - * to preserve the historical behavior that such a failure will be raised - * only if the table currently contains some rows.) - * * Note: we use build_column_default, and not just the cooked default * returned by AddRelationNewConstraints, so that the right thing happens * when a datatype's default applies. @@ -7455,6 +7446,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, { bool has_domain_constraints; bool has_missing = false; + bool has_volatile = false; /* * For an identity column, we can't use build_column_default(), @@ -7472,8 +7464,18 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, else defval = (Expr *) build_column_default(rel, attribute->attnum); + has_domain_constraints = DomainHaveVolatileConstraints(attribute->atttypid, &has_volatile); + + /* + * Adding column with volatile domain constraint requires table rewrite + */ + if (has_volatile) + { + Assert(has_domain_constraints); + tab->rewrite |= AT_REWRITE_DEFAULT_VAL; + } + /* Build CoerceToDomain(NULL) expression if needed */ - has_domain_constraints = DomainHasConstraints(attribute->atttypid); if (!defval && has_domain_constraints) { Oid baseTypeId; @@ -7515,14 +7517,18 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, * Attempt to skip a complete table rewrite by storing the * specified DEFAULT value outside of the heap. This is only * allowed for plain relations and non-generated columns, and the - * default expression can't be volatile (stable is OK). Note that - * contain_volatile_functions deems CoerceToDomain immutable, but - * here we consider that coercion to a domain with constraints is - * volatile; else it might fail even when the table is empty. + * default expression can't be volatile (stable is OK), and the + * domain constraint expression can't be volatile (stable is OK). + * + * Note that contain_volatile_functions deems CoerceToDomain + * immutable. However we have computed CoerceToDomain is volatile + * or not via DomainHaveVolatileConstraints. We use soft error + * evaluation of CoerceToDomain, if evaluation failed, then set + * table rewrite to true. */ if (rel->rd_rel->relkind == RELKIND_RELATION && !colDef->generated && - !has_domain_constraints && + !has_volatile && !contain_volatile_functions((Node *) defval)) { EState *estate; @@ -7532,10 +7538,18 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, /* Evaluate the default expression */ estate = CreateExecutorState(); - exprState = ExecPrepareExpr(defval, estate); + exprState = ExecPrepareExprSafe(defval, estate); + missingval = ExecEvalExpr(exprState, GetPerTupleExprContext(estate), &missingIsNull); + + if (SOFT_ERROR_OCCURRED(exprState->escontext)) + { + missingIsNull = true; + tab->rewrite |= AT_REWRITE_DEFAULT_VAL; + } + /* If it turns out NULL, nothing to do; else store it */ if (!missingIsNull) { diff --git a/src/test/regress/expected/fast_default.out b/src/test/regress/expected/fast_default.out index ccbcdf8403f..aa522cf8bfa 100644 --- a/src/test/regress/expected/fast_default.out +++ b/src/test/regress/expected/fast_default.out @@ -317,11 +317,55 @@ SELECT a, b, length(c) = 3 as c_ok, d, e >= 10 as e_ok FROM t2; 2 | 3 | t | {This,is,abcd,the,real,world} | t (2 rows) +---test fast default over domains with constraints +CREATE DOMAIN domain5 AS int check(value > 10) default 8; +CREATE DOMAIN domain6 as int not null; +CREATE DOMAIN domain7 as int check(value > 10) DEFAULT random(min=>11, max=>100); +CREATE DOMAIN domain8 as int check((value + random(min=>11::int, max=>11)) > 12); +--tests with non-empty table. +CREATE TABLE t3(a int); +INSERT INTO t3 VALUES(1),(2); +ALTER TABLE t3 ADD COLUMN b domain5; --table rewrite, then fail +NOTICE: rewriting table t3 for reason 2 +ERROR: value for domain domain5 violates check constraint "domain5_check" +ALTER TABLE t3 ADD COLUMN b domain6; --table rewrite, then fail +NOTICE: rewriting table t3 for reason 2 +ERROR: domain domain6 does not allow null values +ALTER TABLE t3 ADD COLUMN b domain5 default 12; --no table rewrite +ALTER TABLE t3 ADD COLUMN c domain6 default 13; --no table rewrite +--explicit column default expression override domain's default +--expression, so no need table rewrite. +ALTER TABLE t3 ADD COLUMN d domain7 default 14; +SELECT attnum, attname, atthasmissing, atthasdef, attmissingval +FROM pg_attribute +WHERE attnum > 0 AND attrelid = 't3'::regclass AND attisdropped is false +AND atthasmissing +ORDER BY attnum; + attnum | attname | atthasmissing | atthasdef | attmissingval +--------+---------+---------------+-----------+--------------- + 2 | b | t | t | {12} + 3 | c | t | t | {13} + 4 | d | t | t | {14} +(3 rows) + +--need table rewrite when we are applying domain volatile default expresion +ALTER TABLE t3 ADD COLUMN e domain7; +NOTICE: rewriting table t3 for reason 2 +-- need table rewrite when new column is domain with volatile constraints. +ALTER TABLE t3 ADD COLUMN f domain8 default 14; +NOTICE: rewriting table t3 for reason 2 +ALTER TABLE t3 ADD COLUMN f1 domain8; +NOTICE: rewriting table t3 for reason 2 DROP TABLE t2; +DROP TABLE t3; DROP DOMAIN domain1; DROP DOMAIN domain2; DROP DOMAIN domain3; DROP DOMAIN domain4; +DROP DOMAIN domain5; +DROP DOMAIN domain6; +DROP DOMAIN domain7; +DROP DOMAIN domain8; DROP FUNCTION foo(INT); -- Fall back to full rewrite for volatile expressions CREATE TABLE T(pk INT NOT NULL PRIMARY KEY); diff --git a/src/test/regress/sql/fast_default.sql b/src/test/regress/sql/fast_default.sql index 068dd0bc8aa..269a39edf77 100644 --- a/src/test/regress/sql/fast_default.sql +++ b/src/test/regress/sql/fast_default.sql @@ -287,11 +287,48 @@ ORDER BY attnum; SELECT a, b, length(c) = 3 as c_ok, d, e >= 10 as e_ok FROM t2; +---test fast default over domains with constraints +CREATE DOMAIN domain5 AS int check(value > 10) default 8; +CREATE DOMAIN domain6 as int not null; +CREATE DOMAIN domain7 as int check(value > 10) DEFAULT random(min=>11, max=>100); +CREATE DOMAIN domain8 as int check((value + random(min=>11::int, max=>11)) > 12); + +--tests with non-empty table. +CREATE TABLE t3(a int); +INSERT INTO t3 VALUES(1),(2); + +ALTER TABLE t3 ADD COLUMN b domain5; --table rewrite, then fail +ALTER TABLE t3 ADD COLUMN b domain6; --table rewrite, then fail +ALTER TABLE t3 ADD COLUMN b domain5 default 12; --no table rewrite +ALTER TABLE t3 ADD COLUMN c domain6 default 13; --no table rewrite + +--explicit column default expression override domain's default +--expression, so no need table rewrite. +ALTER TABLE t3 ADD COLUMN d domain7 default 14; + +SELECT attnum, attname, atthasmissing, atthasdef, attmissingval +FROM pg_attribute +WHERE attnum > 0 AND attrelid = 't3'::regclass AND attisdropped is false +AND atthasmissing +ORDER BY attnum; + +--need table rewrite when we are applying domain volatile default expresion +ALTER TABLE t3 ADD COLUMN e domain7; + +-- need table rewrite when new column is domain with volatile constraints. +ALTER TABLE t3 ADD COLUMN f domain8 default 14; +ALTER TABLE t3 ADD COLUMN f1 domain8; + DROP TABLE t2; +DROP TABLE t3; DROP DOMAIN domain1; DROP DOMAIN domain2; DROP DOMAIN domain3; DROP DOMAIN domain4; +DROP DOMAIN domain5; +DROP DOMAIN domain6; +DROP DOMAIN domain7; +DROP DOMAIN domain8; DROP FUNCTION foo(INT); -- Fall back to full rewrite for volatile expressions -- 2.34.1
From b2c056c2e0d5d7ca52bcb859beb2416f8802cb21 Mon Sep 17 00:00:00 2001 From: jian he <jian.universal...@gmail.com> Date: Mon, 1 Sep 2025 13:47:47 +0800 Subject: [PATCH v7 4/4] table scan only when adding domain with volatile constraints When adding a new column, a table scan is sufficient to evaluate domains with volatile check constraints. example demo: CREATE DOMAIN domain8 as int check((value + random(min=>11::int, max=>11)) > 12); CREATE TABLE t3(a int); INSERT INTO t3 VALUES(1),(2); ALTER TABLE t3 ADD COLUMN f domain8 default 1; --error while coercing to domain ALTER TABLE t3 ADD COLUMN f domain8 default 20; --ok discussion: https://postgr.es/m/cacjufxe_+izbr1i49k_ahigpppwltji6km8nosc7fwvkdem...@mail.gmail.com --- src/backend/commands/tablecmds.c | 72 ++++++++++++++++++---- src/test/regress/expected/fast_default.out | 27 +++++++- src/test/regress/sql/fast_default.sql | 15 ++++- 3 files changed, 98 insertions(+), 16 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index c58094a39c0..fcb378e79d5 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -230,6 +230,9 @@ typedef struct NewConstraint * are just copied from the old table during ATRewriteTable. Note that the * expr is an expression over *old* table values, except when is_generated * is true; then it is an expression over columns of the *new* tuple. + * + * If scan_only is true, it means in phase3, table scan is enough to evaulate expr. + * Currently, this is supported when expr is CoerceToDomain. */ typedef struct NewColumnValue { @@ -237,6 +240,8 @@ typedef struct NewColumnValue Expr *expr; /* expression to compute */ ExprState *exprstate; /* execution state */ bool is_generated; /* is it a GENERATED expression? */ + bool scan_only; /* use table scan to evaulate expression, + * useful only when table rewrite is false */ } NewColumnValue; /* @@ -6008,7 +6013,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, * rebuild data. */ if (tab->constraints != NIL || tab->verify_new_notnull || - tab->partition_constraint != NULL) + tab->partition_constraint != NULL || + tab->newvals) ATRewriteTable(tab, InvalidOid); /* @@ -6118,7 +6124,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) Relation newrel; TupleDesc oldTupDesc; TupleDesc newTupDesc; + TupleDesc oldTupDescTemp; bool needscan = false; + bool scan_only = false; List *notnull_attrs; List *notnull_virtual_attrs; int i; @@ -6135,6 +6143,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) */ oldrel = table_open(tab->relid, NoLock); oldTupDesc = tab->oldDesc; + oldTupDescTemp = CreateTupleDescCopy(oldTupDesc); newTupDesc = RelationGetDescr(oldrel); /* includes all mods */ if (OidIsValid(OIDNewHeap)) @@ -6203,6 +6212,11 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) /* expr already planned */ ex->exprstate = ExecInitExpr((Expr *) ex->expr, NULL); + if (ex->scan_only && !tab->rewrite && !scan_only) + { + needscan = true; + scan_only = true; + } } notnull_attrs = notnull_virtual_attrs = NIL; @@ -6431,6 +6445,43 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) * new constraints etc. */ insertslot = oldslot; + + /* + * The tupdesc (newTupDesc) in oldslot already have the updated + * attribute changes. If we use it for below ExecEvalExpr, then + * CheckVarSlotCompatibility will fail. Therefore, we need to + * temporarily set oldslot's tts_tupleDescriptor equal to oldTupDesc. + * Essentially, what we're doing here is evaluating the + * CoerceToDomain node against the existing table slot. + */ + if (scan_only) + { + Datum values pg_attribute_unused(); + bool isnull pg_attribute_unused(); + + insertslot->tts_tupleDescriptor = oldTupDescTemp; + econtext->ecxt_scantuple = insertslot; + + foreach(l, tab->newvals) + { + NewColumnValue *ex = lfirst(l); + + if (!ex->scan_only) + continue; + + /* + * we can not use ExecEvalExprNoReturn here, because we + * use ExecInitExpr compile NewColumnValue->expr. Here, + * we only check whether the oldslot value satisfies the + * domain constraint. So it is ok to override the value + * evaluated by ExecEvalExpr. + */ + values = ExecEvalExpr(ex->exprstate, econtext, &isnull); + values = (Datum) 0; + isnull = true; + } + insertslot->tts_tupleDescriptor = newTupDesc; + } } /* Now check any constraints on the possibly-changed tuple */ @@ -7466,15 +7517,6 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, has_domain_constraints = DomainHaveVolatileConstraints(attribute->atttypid, &has_volatile); - /* - * Adding column with volatile domain constraint requires table rewrite - */ - if (has_volatile) - { - Assert(has_domain_constraints); - tab->rewrite |= AT_REWRITE_DEFAULT_VAL; - } - /* Build CoerceToDomain(NULL) expression if needed */ if (!defval && has_domain_constraints) { @@ -7511,6 +7553,13 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, newval->expr = defval; newval->is_generated = (colDef->generated != '\0'); + /* + * If the domain has volatile constraints, table scan is enough to + * evaluate CoerceToDomain, no need table rewrite. + */ + if (has_volatile && IsA(defval, CoerceToDomain)) + newval->scan_only = true; + tab->newvals = lappend(tab->newvals, newval); /* @@ -7528,7 +7577,6 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, */ if (rel->rd_rel->relkind == RELKIND_RELATION && !colDef->generated && - !has_volatile && !contain_volatile_functions((Node *) defval)) { EState *estate; @@ -7560,7 +7608,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, } FreeExecutorState(estate); } - else + else if (!has_volatile) { /* * Failed to use missing mode. We have to do a table rewrite diff --git a/src/test/regress/expected/fast_default.out b/src/test/regress/expected/fast_default.out index aa522cf8bfa..136454a289d 100644 --- a/src/test/regress/expected/fast_default.out +++ b/src/test/regress/expected/fast_default.out @@ -351,11 +351,34 @@ ORDER BY attnum; --need table rewrite when we are applying domain volatile default expresion ALTER TABLE t3 ADD COLUMN e domain7; NOTICE: rewriting table t3 for reason 2 --- need table rewrite when new column is domain with volatile constraints. -ALTER TABLE t3 ADD COLUMN f domain8 default 14; +--No need table rewrite when we are applying domain constraint volatile expresion, +--but table scan is required. +ALTER TABLE t3 ADD COLUMN f domain8 default 1; --error while coercing to domain NOTICE: rewriting table t3 for reason 2 +ERROR: value for domain domain8 violates check constraint "domain8_check" +ALTER TABLE t3 ADD COLUMN f domain8 default 20; --ok +ALTER DOMAIN domain8 ADD CHECK(VALUE IS NOT NULL); +--table rewrite then error, because f1 default value (NULL), does not satisfied with domain constraint ALTER TABLE t3 ADD COLUMN f1 domain8; NOTICE: rewriting table t3 for reason 2 +ERROR: value for domain domain8 violates check constraint "domain8_check1" +SELECT f FROM t3; + f +---- + 20 + 20 +(2 rows) + +SELECT attnum, attname, atthasmissing, atthasdef, attmissingval +FROM pg_attribute +WHERE attnum > 0 AND attrelid = 't3'::regclass AND attisdropped is false +AND atthasmissing +ORDER BY attnum; + attnum | attname | atthasmissing | atthasdef | attmissingval +--------+---------+---------------+-----------+--------------- + 6 | f | t | t | {20} +(1 row) + DROP TABLE t2; DROP TABLE t3; DROP DOMAIN domain1; diff --git a/src/test/regress/sql/fast_default.sql b/src/test/regress/sql/fast_default.sql index 269a39edf77..178249de960 100644 --- a/src/test/regress/sql/fast_default.sql +++ b/src/test/regress/sql/fast_default.sql @@ -315,10 +315,21 @@ ORDER BY attnum; --need table rewrite when we are applying domain volatile default expresion ALTER TABLE t3 ADD COLUMN e domain7; --- need table rewrite when new column is domain with volatile constraints. -ALTER TABLE t3 ADD COLUMN f domain8 default 14; +--No need table rewrite when we are applying domain constraint volatile expresion, +--but table scan is required. +ALTER TABLE t3 ADD COLUMN f domain8 default 1; --error while coercing to domain +ALTER TABLE t3 ADD COLUMN f domain8 default 20; --ok +ALTER DOMAIN domain8 ADD CHECK(VALUE IS NOT NULL); +--table rewrite then error, because f1 default value (NULL), does not satisfied with domain constraint ALTER TABLE t3 ADD COLUMN f1 domain8; +SELECT f FROM t3; +SELECT attnum, attname, atthasmissing, atthasdef, attmissingval +FROM pg_attribute +WHERE attnum > 0 AND attrelid = 't3'::regclass AND attisdropped is false +AND atthasmissing +ORDER BY attnum; + DROP TABLE t2; DROP TABLE t3; DROP DOMAIN domain1; -- 2.34.1
From da5efb88c112d49a8800e81f857006bb58deee92 Mon Sep 17 00:00:00 2001 From: jian he <jian.universal...@gmail.com> Date: Mon, 19 May 2025 11:11:01 +0800 Subject: [PATCH v7 2/4] add function DomainHaveVolatileConstraints the signature is: extern bool DomainHaveVolatileConstraints(Oid type_id, bool *have_volatile); Returns true if the Domain has any constraints. If you want check this domain have any volatile check constraints, make sure have_volatile is not NULL. discussion: https://postgr.es/m/cacjufxe_+izbr1i49k_ahigpppwltji6km8nosc7fwvkdem...@mail.gmail.com --- src/backend/utils/cache/typcache.c | 40 ++++++++++++++++++++++++++++++ src/include/utils/typcache.h | 1 + 2 files changed, 41 insertions(+) diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c index 6a347698edf..da9ed0d29c8 100644 --- a/src/backend/utils/cache/typcache.c +++ b/src/backend/utils/cache/typcache.c @@ -1499,6 +1499,46 @@ DomainHasConstraints(Oid type_id) } +/* + * Check whether a domain has any constraints, and determine if any of those + * constraints contain volatile expressions. + * + * To detect volatile expressions within domain check constraints, ensure that + * have_volatile is not NULL. If have_volatile is NULL, the behavior is + * equivalent to that of DomainHasConstraints. + */ +bool +DomainHaveVolatileConstraints(Oid type_id, bool *have_volatile) +{ + TypeCacheEntry *typentry; + + /* + * Note: a side effect is to cause the typcache's domain data to become + * valid. This is fine since we'll likely need it soon if there is any. + */ + typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO); + + if (typentry->domainData != NULL) + { + ListCell *lc; + + foreach(lc, typentry->domainData->constraints) + { + DomainConstraintState *r = (DomainConstraintState *) lfirst(lc); + + if (r->constrainttype == DOM_CONSTRAINT_CHECK && + contain_volatile_functions((Node *) r->check_expr)) + { + if (have_volatile) + *have_volatile = true; + break; + } + } + return true; + } + return false; +} + /* * array_element_has_equality and friends are helper routines to check * whether we should believe that array_eq and related functions will work diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h index 1cb30f1818c..aa1c86e35c3 100644 --- a/src/include/utils/typcache.h +++ b/src/include/utils/typcache.h @@ -184,6 +184,7 @@ extern void InitDomainConstraintRef(Oid type_id, DomainConstraintRef *ref, extern void UpdateDomainConstraintRef(DomainConstraintRef *ref); extern bool DomainHasConstraints(Oid type_id); +extern bool DomainHaveVolatileConstraints(Oid type_id, bool *have_volatile); extern TupleDesc lookup_rowtype_tupdesc(Oid type_id, int32 typmod); -- 2.34.1
From 12f6bfc936b1d51d7956b035066eacafe287c3f7 Mon Sep 17 00:00:00 2001 From: jian he <jian.universal...@gmail.com> Date: Mon, 24 Mar 2025 16:07:46 +0800 Subject: [PATCH v7 1/4] soft error variant of ExecPrepareExpr, ExecInitExpr ExecInitExprSafe: soft error of ExecInitExpr. ExecPrepareExprSafe: soft error of ExecPrepareExpr. ExecPrepareExprSafe initialize for expression execution with soft error support. not all expression node support it. Like node CoerceToDomain support it. XXX more comments. discussion: https://postgr.es/m/cacjufxe_+izbr1i49k_ahigpppwltji6km8nosc7fwvkdem...@mail.gmail.com --- src/backend/executor/execExpr.c | 63 +++++++++++++++++++++++++++++++++ src/include/executor/executor.h | 2 ++ 2 files changed, 65 insertions(+) diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index f1569879b52..9182ba446a0 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -170,6 +170,47 @@ ExecInitExpr(Expr *node, PlanState *parent) return state; } +/* + * ExecInitExprSafe: soft error variant of ExecInitExpr. + * + * use it only for expression nodes support soft errors, not all expression + * nodes support it. +*/ +ExprState * +ExecInitExprSafe(Expr *node, PlanState *parent) +{ + ExprState *state; + ExprEvalStep scratch = {0}; + + /* Special case: NULL expression produces a NULL ExprState pointer */ + if (node == NULL) + return NULL; + + /* Initialize ExprState with empty step list */ + state = makeNode(ExprState); + state->expr = node; + state->parent = parent; + state->ext_params = NULL; + state->escontext = makeNode(ErrorSaveContext); + state->escontext->type = T_ErrorSaveContext; + state->escontext->error_occurred = false; + state->escontext->details_wanted = true; + + /* Insert setup steps as needed */ + ExecCreateExprSetupSteps(state, (Node *) node); + + /* Compile the expression proper */ + ExecInitExprRec(node, state, &state->resvalue, &state->resnull); + + /* Finally, append a DONE step */ + scratch.opcode = EEOP_DONE_RETURN; + ExprEvalPushStep(state, &scratch); + + ExecReadyExpr(state); + + return state; +} + /* * ExecInitExprWithParams: prepare a standalone expression tree for execution * @@ -778,6 +819,28 @@ ExecPrepareExpr(Expr *node, EState *estate) return result; } +/* + * ExecPrepareExprSafe: soft error variant of ExecPrepareExpr. + * + * use it when expression node *support* soft error expression execution. + */ +ExprState * +ExecPrepareExprSafe(Expr *node, EState *estate) +{ + ExprState *result; + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); + + node = expression_planner(node); + + result = ExecInitExprSafe(node, NULL); + + MemoryContextSwitchTo(oldcontext); + + return result; +} + /* * ExecPrepareQual --- initialize for qual execution outside a normal * Plan tree context. diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 10dcea037c3..eb5ecf9d59f 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -320,6 +320,7 @@ ExecProcNode(PlanState *node) * prototypes from functions in execExpr.c */ extern ExprState *ExecInitExpr(Expr *node, PlanState *parent); +extern ExprState *ExecInitExprSafe(Expr *node, PlanState *parent); extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params); extern ExprState *ExecInitQual(List *qual, PlanState *parent); extern ExprState *ExecInitCheck(List *qual, PlanState *parent); @@ -368,6 +369,7 @@ extern ProjectionInfo *ExecBuildUpdateProjection(List *targetList, TupleTableSlot *slot, PlanState *parent); extern ExprState *ExecPrepareExpr(Expr *node, EState *estate); +extern ExprState *ExecPrepareExprSafe(Expr *node, EState *estate); extern ExprState *ExecPrepareQual(List *qual, EState *estate); extern ExprState *ExecPrepareCheck(List *qual, EState *estate); extern List *ExecPrepareExprList(List *nodes, EState *estate); -- 2.34.1