Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1tqard-007Rkw-Tt for pgsql-hackers@arkaria.postgresql.org; Fri, 07 Mar 2025 16:47:58 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1tqarc-00BdrL-Jv for pgsql-hackers@arkaria.postgresql.org; Fri, 07 Mar 2025 16:47:56 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1tqarc-00BdrD-8n for pgsql-hackers@lists.postgresql.org; Fri, 07 Mar 2025 16:47:56 +0000 Received: from mail-yw1-x112b.google.com ([2607:f8b0:4864:20::112b]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.96) (envelope-from ) id 1tqarY-001X5Z-2r for pgsql-hackers@postgresql.org; Fri, 07 Mar 2025 16:47:55 +0000 Received: by mail-yw1-x112b.google.com with SMTP id 00721157ae682-6fd6f7f8df9so17843827b3.1 for ; Fri, 07 Mar 2025 08:47:54 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1741366072; x=1741970872; darn=postgresql.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=5o4LBJsssrCaCndGcvScSBlNWM3MQek/xJV7BWR3rHI=; b=LJ/qJsV2nwQtI19qx4yM5zp9+dugESWj99PaIG9FbKkp8vaf0oNJCKmyetNAMh8C4q 4ec6XPxjWjsBeP/tee0srwcrSTlzfYE+GfVdl8IrSgpPiP3OBmYP77mPAZAIMEyw3BdB +v+L6BhMb/Q1yH+I9hxgzdyYkx2uEoz5pdkGRJofAcrw3B+ZW2yVbrjEWbIDeUZxOACk Tji4UCvnL8c4NIo8gWih5RLe9xbIGFfvlIuB5T5rIBAzG7L2BmhFXGtmpGT9sxQZqGtG 6szx/BPKkSjBidHiDsJBbYv7gu72ax/BE88GSDAWWu25wq58qh+BRhyrP9oJlMpCF6no oJ+A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1741366072; x=1741970872; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=5o4LBJsssrCaCndGcvScSBlNWM3MQek/xJV7BWR3rHI=; b=SNDjx60lTJmwzpnPFMFkVdmURpvQLu9nNT+AuA7ii8/l4/If5bvm1Fn4dnoHq4ogBY D94yS8GN01xXNolN6Aw7Ql10c79jcYEBh2o46nQZpY2bbyl/Wo2COl8YWroAkCYg9qLb vZiVupDs1knfrK+UB7i4JC/LY+aCONMjIy1YZGpnmtXXFvAm250SLCS22MQR3V6kMr3g 6Gw4COUgOacDzs2WY8v3qSf/WshG5vSq7f4H6gCTL2KTUa8gHmpqNrhliGxUomJAiugH Y7GjbBQKILe/1MtKbhhHkZgajA+DbfzyXSWDfF4OE0wXrrnItTzL16RiO+AVdrxyUe2P Pulg== X-Forwarded-Encrypted: i=1; AJvYcCXdiSwISwoU+e1n147pj5ioZHuBBlVgJ4sCWdNT7ukjLxttkI3xBZdNa01827Wv4MLPu8J4yQhN8RNmcu5t@postgresql.org X-Gm-Message-State: AOJu0Yw/xNO1WqihXkpcTuaoXQ92wmOeeJ3QLgnwrjdPF6pRe64OXlAZ pEMAdyIuE0h7BObqOq7U48C6xYs9MAcDpApyg9Rn0CrNLRCL18RzYQJLCn5WdxwtQK+nLQmMctS rWI+83wbhMLZnH9DCC6s1s1ItKEnc5hGw X-Gm-Gg: ASbGncsLFSjV9ZDwc8uptWRtT5iNGdY2hqJWxaU9K7+Ycw2jkyPRJwUoqK0d7eE57Uq 0bd4P9tx1HGY+3HhdyBvPRjjGoS95Ond0cxs654dIO57W4WixPiWPcsA1hbZpL73b+Y8WoZ/mA9 8LcJ31XS76Wq7MUb7vYBDLbnu7qRgBrfKu+hNIkBzMmdkUb6YNb2UYlK9H X-Google-Smtp-Source: AGHT+IHITpxGkH2mG2Sy7hWaiiUKHH2mw0dsTjQl0jqyXwLbfgcdrn+So4Edc+MMlufE+eWKbuWBxRqbvSriX5AuBMo= X-Received: by 2002:a05:690c:680d:b0:6fe:ad31:7724 with SMTP id 00721157ae682-6fecf78edf6mr3038217b3.10.1741366072497; Fri, 07 Mar 2025 08:47:52 -0800 (PST) MIME-Version: 1.0 References: In-Reply-To: From: Navneet Kumar Date: Fri, 7 Mar 2025 22:17:41 +0530 X-Gm-Features: AQ5f1Jrt4-VrdhDV22rl6hfF9f9yO6qPOvdgHopiBuat-qzgTgDLK4xcdOOTnwE Message-ID: Subject: Re: support virtual generated column not null constraint To: jian he Cc: ego alter , PostgreSQL-development Content-Type: multipart/alternative; boundary="0000000000002cdfc5062fc362d7" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --0000000000002cdfc5062fc362d7 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Hi I encountered an issue when trying to add a virtual column to an existing table using the ALTER command. The operation fails even though the existing data ensures that the virtual column's value will never be NULL. However, if I define the same virtual column while creating the table and then insert the same data, it works as expected. For example This scenario fails 1. CREATE TABLE person ( id INT GENERATED BY DEFAULT AS IDENTITY, first_name VARCHAR(50) NOT NULL, last_name VARCHAR(50) NOT NULL ); 2. INSERT INTO person (first_name, last_name) VALUES ('first', 'last'); 3. ALTER TABLE person ADD COLUMN full_name VARCHAR(100) GENERATED ALWAYS AS (first_name || ' ' || last_name) VIRTUAL; The above ALTER command works if I use STORED instead. Also if I define virtual generated column while creating table it works with same data. 1. CREATE TABLE person ( id INT GENERATED BY DEFAULT AS IDENTITY, first_name VARCHAR(50) NOT NULL, last_name VARCHAR(50) NOT NULL, full_name VARCHAR(100) NOT NULL GENERATED ALWAYS AS (first_name || ' ' || last_name) VIRTUAL ); 2. INSERT INTO person(first_name, last_name) VALUES ('first', 'last'); On Thu, Mar 6, 2025 at 7:15=E2=80=AFPM jian he wrote: > On Wed, Feb 26, 2025 at 3:01=E2=80=AFPM ego alter = wrote: > > > > Hi, I=E2=80=99ve had a chance to review the patch. As I am still getti= ng > familiar with executor part, questions and feedbacks could be relatively > trivial. > > > > There are two minor issues i want to discuss: > > 1. The way of caching constraint-checking expr for virtual generated no= t > null > > The existing array for caching constraint-checking expr is > > /* array of constraint-checking expr states */ > > ExprState **ri_ConstraintExprs; > > > > the proposed changes for virtual generated not null in patch > > + /* array of virtual generated not null constraint-checking expr > states */ > > + ExprState **ri_VirGeneratedConstraintExprs; > > /* > > Could you explain the rationale behind adding this new field instead of > reusing ri_ConstraintExprs? The comment suggests it=E2=80=99s used specif= ically for > not null constraint-checking, but could it be extended to handle other > constraints in the future as well? I assume one benefit is that it > separates the handling of normal constraints from virtual ones, but I'd > like to appreciate more context on the decision. > > > > it's a cache mechanism, just like ResultRelInfo.ri_ConstraintExprs > you can see comments in ExecRelCheck > /* > * If first time through for this result relation, build expression > * nodetrees for rel's constraint expressions. Keep them in the > per-query > * memory context so they'll survive throughout the query. > */ > > for example: > create table t(a int, check (a > 3)); > insert into t values (4),(3); > we need to call ExecRelCheck for values 4 and 3. > The second time ExecRelCheck called for values 3, if > ri_ConstraintExprs is not null then > we didn't need to call ExecPrepareExpr again for the same check > constraint expression. > > + ExprState **ri_VirGeneratedConstraintExprs; > is specifically only for virtual generated columns not null constraint > evaluation. > Anyway, I renamed it to ri_GeneratedNotNullExprs. > > If you want to extend cache for other constraints in the future, > you can add it to struct ResultRelInfo. > Currently struct ResultRelInfo, we have ri_GeneratedExprsU, > ri_GeneratedExprsI for generated expressions; > ri_ConstraintExprs for check constraints. > > > > 2. The naming of variable gen_virtualnn. > > Gen_virtualnn looks confusing at first glance. Checkconstr seems to be > more straightforward. > > > thanks. I changed it to exprstate. > > new patch attached. > 0001 not null for virtual generated columns, some refactoring happened > within ExecConstraints > to avoid duplicated error handling code. > > 0002 and 0003 is the domain for virtual generated columns. domain can > have not-null constraints, > this is built on top of 0001, so I guess posting here for review should b= e > fine? > > currently it works as intended. but add a virtual generated column > with domain_with_constraints > requires table rewrite. but some cases table scan should just be enough. > some case we do need table rewrite. > for example: > create domain d1 as int check(value > random(min=3D>11::int, max=3D>12)); > create domain d2 as int check(value > 12); > create table t(a int); > insert into t select g from generate_series(1, 10) g; > alter table t add column b d1 generated always as (a+11) virtual; --we > do need table rewrite in phase 3. > alter table t add column c d2 generated always as (a + 12) virtual; > --we can only do table scan in phase 3. > --0000000000002cdfc5062fc362d7 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Hi=C2=A0

I encountered an issue when tr= ying to add a virtual column to an existing table using the ALTER command. The operation fails even though the existing data ensures that= the virtual column's value will never be NULL. However, i= f I define the same virtual column while creating the table and then insert= the same data, it works as expected.

For example<= br>This scenario fails
1. CREATE TABLE person (
=C2=A0 =C2=A0 = id INT GENERATED BY DEFAULT AS IDENTITY,
=C2=A0 =C2=A0 first_name VARCHA= R(50) NOT NULL,
=C2=A0 =C2=A0 last_name VARCHAR(50) NOT NULL
);
2. INSERT INTO person (first_name, last_name)
VALUES ('first'= , 'last');

3. ALTER TABLE person
ADD COLUMN full_name VAR= CHAR(100) GENERATED ALWAYS AS (first_name || ' ' || last_name) VIRT= UAL;

The above ALTER command works if I use STORED instead. Al= so if I define virtual generated column while creating table it works with = same data.

1. CREATE TABLE person (
=C2=A0 =C2=A0 id INT GENERATE= D BY DEFAULT AS IDENTITY,
=C2=A0 =C2=A0 first_name VARCHAR(50) NOT NULL,=
=C2=A0 =C2=A0 last_name VARCHAR(50) NOT NULL,
=C2=A0 =C2=A0 full_nam= e VARCHAR(100) NOT NULL GENERATED ALWAYS AS (first_name || '=C2=A0 '= ; ||=C2=A0 =C2=A0 =C2=A0 last_name) VIRTUAL
);

2. INSERT INTO per= son(first_name, last_name)=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0
=C2=A0 =C2=A0 VALUES ('first', 'last');
<= br>




On Thu, Mar 6, 2025 at 7:15=E2=80=AFPM jian he <jian.universali= ty@gmail.com> wrote:
On Wed, Feb 26, 2025 at 3:01=E2=80=AFPM ego alter <xunengzhou@gmail.com= > wrote:
>
> Hi, I=E2=80=99ve had a chance to review the patch.=C2=A0 As I am still= getting familiar with executor part, questions and feedbacks could be rela= tively trivial.
>
> There are two minor issues i want to discuss:
> 1. The way of caching constraint-checking expr for virtual generated n= ot null
> The existing array for caching constraint-checking expr is
>=C2=A0 /* array of constraint-checking expr states */
> ExprState **ri_ConstraintExprs;
>
> the proposed changes for virtual generated not null in patch
> +=C2=A0 /* array of virtual generated not null constraint-checking exp= r states */
> +=C2=A0 ExprState **ri_VirGeneratedConstraintExprs;
> /*
> Could you explain the rationale behind adding this new field instead o= f reusing ri_ConstraintExprs? The comment suggests it=E2=80=99s used specif= ically for not null constraint-checking, but could it be extended to handle= other constraints in the future as well? I assume one benefit is that it s= eparates the handling of normal constraints from virtual ones, but I'd = like to appreciate more context on the decision.
>

it's a cache mechanism, just like ResultRelInfo.ri_ConstraintExprs
you can see comments in ExecRelCheck
=C2=A0 =C2=A0 /*
=C2=A0 =C2=A0 =C2=A0* If first time through for this result relation, build= expression
=C2=A0 =C2=A0 =C2=A0* nodetrees for rel's constraint expressions.=C2=A0= Keep them in the per-query
=C2=A0 =C2=A0 =C2=A0* memory context so they'll survive throughout the = query.
=C2=A0 =C2=A0 =C2=A0*/

for example:
create table t(a int, check (a > 3));
insert into t values (4),(3);
we need to call ExecRelCheck for values 4 and 3.
The second time ExecRelCheck called for values 3, if
ri_ConstraintExprs is not null then
we didn't need to call ExecPrepareExpr again for the same check
constraint expression.

+=C2=A0 ExprState **ri_VirGeneratedConstraintExprs;
is specifically only for virtual generated columns not null constraint
evaluation.
Anyway, I renamed it to ri_GeneratedNotNullExprs.

If you want to extend cache for other constraints in the future,
you can add it to struct ResultRelInfo.
Currently struct ResultRelInfo, we have ri_GeneratedExprsU,
ri_GeneratedExprsI for generated expressions;
ri_ConstraintExprs for check constraints.


> 2. The naming of variable gen_virtualnn.
> Gen_virtualnn looks confusing at first glance. Checkconstr seems to be= more straightforward.
>
thanks. I changed it to exprstate.

new patch attached.
0001 not null for virtual generated columns, some refactoring happened
within ExecConstraints
to avoid duplicated error handling code.

0002 and 0003 is the domain for virtual generated columns. domain can
have not-null constraints,
this is built on top of 0001, so I guess posting here for review should be = fine?

currently it works as intended. but add a virtual generated column
with domain_with_constraints
requires table rewrite. but some cases table scan should just be enough. some case we do need table rewrite.
for example:
create domain d1 as int check(value > random(min=3D>11::int, max=3D&g= t;12));
create domain d2 as int check(value > 12);
create table t(a int);
insert into t select g from generate_series(1, 10) g;
alter table t add column b d1 generated always as (a+11) virtual; --we
do need table rewrite in phase 3.
alter table t add column c d2 generated always as (a + 12) virtual;
--we can only do table scan in phase 3.
--0000000000002cdfc5062fc362d7--