bug in stored generated column over domain with constraints.
hi.
create domain d1 as int not null;
create domain d2 as int check (value > 1);
create domain d3 as int check (value is not null);
create table t0(b int, a d3 GENERATED ALWAYS as (b + 11) stored);
insert into t0 values (1, default);
ERROR: value for domain d3 violates check constraint "d3_check"
in the above example, a domain with check constraint not working as intended,
similarly, a domain with not-null constraint will also not work.
now table t0 can not insert any data, because of check constraint violation.
so i think this is a bug, (hope i didn't miss anything).
in ExecBuildProjectionInfo, we compile "values (1, default)" as
targetlist expression (CONST, COERCETODOMAIN)
Then in ExecResult, ExecProject, ExecInterpExpr we evaluate the
compiled expression;
we failed at ExecEvalConstraintCheck. we are acting like: ``null::d3``.
explain(costs off, verbose) insert into t0 values (1, default);
QUERY PLAN
----------------------------------------
Insert on public.t0
-> Result
Output: 1, NULL::integer
the plan is ``NULL::integer``, which will not fail, but ``NULL::d3`` will fail.
that means, the output plan is right. it's the execution wrong?
the main fix should be in rewriteTargetListIU.
UPDATE don't have this issue, since there is no Result node,
ExecComputeStoredGenerated will do the domain constraint check.
related:
https://git.postgresql.org/cgit/postgresql.git/commit/?id=0da39aa7667b06e16189d318f7850d559d446d52
Attachments:
v1-0001-fix-default-insertion-for-stored-generated-column.patchtext/x-patch; charset=US-ASCII; name=v1-0001-fix-default-insertion-for-stored-generated-column.patchDownload+102-13
hi.
comments refined and minor aesthetic adjustments made.
Attachments:
v2-0001-fix-default-insertion-for-stored-generated-column.patchtext/x-patch; charset=US-ASCII; name=v2-0001-fix-default-insertion-for-stored-generated-column.patchDownload+118-18
hi.
new patch attached.
rewriteTargetListIU, expand_insert_targetlist these two places can
make a null Const TargetEntry for the generated column in an INSERT
operation.
but since this problem only occurs in INSERT, so i placed the logic
within expand_insert_targetlist would be appropriate?
The following are excerpts of the commit message.
--------------------------------
create domain d3 as int check (value is not null);
create table t0(b int, a d3 GENERATED ALWAYS as (b + 11) stored);
insert into t0 values (1, default);
ERROR: value for domain d3 violates check constraint "d3_check"
explain(costs off, verbose) insert into t0 values (1, default);
QUERY PLAN
---------------------------------------
Insert on public.t0
-> Result
Output: 1, NULL::integer
For INSERT operation, for Query->targetList, we should not make a
generated column
over domain with constraint to a CoerceToDomain node, instead, we make it as a
simple null Const over domain's base type.
When a column is a generated column in an INSERT, expand_insert_targetlist
should unconditionally generate a null Const to be inserted. If we are not
doing this way, we might end up wrapping the null Const in a CoerceToDomain
node, which may trigger runtime error earlier if the domain has a NOT NULL
constraint. That's not fine, as generated columns are already handled in
ExecComputeStoredGenerated.
--------------------------------
Attachments:
v3-0001-fix-INSERT-generated-column-over-domain-with-cons.patchtext/x-patch; charset=UTF-8; name=v3-0001-fix-INSERT-generated-column-over-domain-with-cons.patchDownload+112-9
jian he <jian.universality@gmail.com> writes:
new patch attached.
I looked this over. It's kind of astonishing that nobody has reported
this before, because AFAICT it's been broken since we invented
generated columns.
rewriteTargetListIU, expand_insert_targetlist these two places can
make a null Const TargetEntry for the generated column in an INSERT
operation.
rewriteTargetListIU will *not* do that: it explicitly does nothing
for an attgenerated column, except apply a bunch of error checks.
See about line 988 in HEAD. So it seems sufficient to fix
expand_insert_targetlist as you've done here. And then we have to
make ExecCheckPlanOutput cope, too.
I think that this patch is technically correct, but I don't like
it much because it pays little attention to keeping
expand_insert_targetlist and ExecCheckPlanOutput readable, and no
attention at all to keeping their logic parallel. I think we can
make the code cleaner by moving the default case to the end, as
in the attached.
The test cases seemed a bit overdone, especially in comparison
to the adjacent existing tests. Fine for development maybe,
but I don't see the value in carrying them forever. On the other
hand, there's a comment at the top of the test script:
-- keep these tests aligned with generated_virtual.sql
The VIRTUAL case is currently rejecting domains altogether.
But perhaps that won't be true forever, so I think we ought
to follow that advice.
So I end with the attached v4.
regards, tom lane
Attachments:
v4-0001-fix-INSERT-generated-column-over-domain-with-cons.patchtext/x-diff; charset=us-ascii; name*0=v4-0001-fix-INSERT-generated-column-over-domain-with-cons.p; name*1=atchDownload+91-28
On Tue, Apr 15, 2025 at 4:10 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
jian he <jian.universality@gmail.com> writes:
new patch attached.
I looked this over. It's kind of astonishing that nobody has reported
this before, because AFAICT it's been broken since we invented
generated columns.rewriteTargetListIU, expand_insert_targetlist these two places can
make a null Const TargetEntry for the generated column in an INSERT
operation.rewriteTargetListIU will *not* do that: it explicitly does nothing
for an attgenerated column, except apply a bunch of error checks.
See about line 988 in HEAD. So it seems sufficient to fix
expand_insert_targetlist as you've done here. And then we have to
make ExecCheckPlanOutput cope, too.I think that this patch is technically correct, but I don't like
it much because it pays little attention to keeping
expand_insert_targetlist and ExecCheckPlanOutput readable, and no
attention at all to keeping their logic parallel. I think we can
make the code cleaner by moving the default case to the end, as
in the attached.
your ExecCheckPlanOutput change makes sense to me.
call getBaseTypeAndTypmod in ExecCheckPlanOutput would be a waste cycle,
given that we will compute the generated column later.
The test cases seemed a bit overdone, especially in comparison
to the adjacent existing tests. Fine for development maybe,
but I don't see the value in carrying them forever. On the other
hand, there's a comment at the top of the test script:
-- keep these tests aligned with generated_virtual.sql
The VIRTUAL case is currently rejecting domains altogether.
But perhaps that won't be true forever, so I think we ought
to follow that advice.
I submitted a patch for the domain over the virtual generated column,
so didn't add such a test on it.
Thanks for simplifying the tests, overall all looks good.
jian he <jian.universality@gmail.com> writes:
Thanks for simplifying the tests, overall all looks good.
OK, pushed and back-patched, but only to v14. I tried to make
a variant that'd work in v13, but it caused ExecCheckPlanOutput
to throw "Query provides a value for a generated column" errors
for UPDATEs. That's surely related to the major refactoring of
UPDATE targetlists that we did in v14. Given that we've had
zero field complaints about this problem so far, I think the
risk of breaking something in v13 exceeds the value of fixing
the bug, so I left v13 alone.
regards, tom lane