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.96) (envelope-from ) id 1wi5AS-0000OW-0o for pgsql-hackers@arkaria.postgresql.org; Fri, 10 Jul 2026 06:57:00 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wi55g-00GM1H-17 for pgsql-hackers@arkaria.postgresql.org; Fri, 10 Jul 2026 06:52:05 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wi55f-00GM18-2w for pgsql-hackers@lists.postgresql.org; Fri, 10 Jul 2026 06:52:04 +0000 Received: from mail-wm1-x331.google.com ([2a00:1450:4864:20::331]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wi55e-00000000R4u-0rw8 for pgsql-hackers@postgresql.org; Fri, 10 Jul 2026 06:52:03 +0000 Received: by mail-wm1-x331.google.com with SMTP id 5b1f17b1804b1-493bf73ec2aso3123995e9.2 for ; Thu, 09 Jul 2026 23:52:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cybertec.at; s=google; t=1783666319; x=1784271119; darn=postgresql.org; h=mime-version:user-agent:content-transfer-encoding:content-type :references:in-reply-to:date:cc:to:from:subject:message-id:from:to :cc:subject:date:message-id:reply-to:content-type; bh=FccFvhDXzY3Gd1GZvqWrvgoE4ABbJYvmf5D4f+iBCcU=; b=GB0bBSwzNMJv06Cxw9oPQUwzTrwvtCM0wmOVckp6iuq+PuYmct4DGDfVw0vWOLW6rR oXuOpkup8UCcrp4untLCzJI1lCQOnDwHiSB8nhOo3D9NttxmSQxu62t0G5XbM1RouZCO eNIQme9eTThQCeT/Y8diVpuMUVlquwNMUU5M0WqrFsdk14sxnF13A/gY+oY+JxSaT4ZA zjUn26603uwe5FkmrX51RImBZpvRpVehdrGSIA5yC31inAzX8Mahsynkp3X3w3rZiaF3 Tn108y2g7Qjql/iuGoCZQD5xfoLUUqhKheUUdSOARd9sirOvcMP4OyNR4qMHTzjXL9Vi ipSA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783666319; x=1784271119; h=mime-version:user-agent:content-transfer-encoding:content-type :references:in-reply-to:date:cc:to:from:subject:message-id:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=FccFvhDXzY3Gd1GZvqWrvgoE4ABbJYvmf5D4f+iBCcU=; b=SUHACArbut34vsN008AQGnY15I2zkPCE5mdAhf14bFuOB7QB3oQIEipqmk/p5yU2os xTeHBpg6RS3HnEs2rp2t55dmCud/BXm6zu4Dg2LJC9R0/SSNGWhStIzyooYd0X04qAC/ q2Mako0AW4SNzINKej2lJNUoHb7HfLTC8s6ezeU+j9DiWLaalSzew00GwTD4ZY67nzle SPZJmnLDsvtlRG0pRUmSgCzKIg7JqP4bzqX1qN49w4VOWdN84bkO0Tq6VSX5semUwfqn eo2pmFe4AGPork+RJYH02mLF0J/9EdRUJTMamgntVPXiFnks22nrdYGgTt0BdCc+EwR8 0miQ== X-Forwarded-Encrypted: i=1; AHgh+RpjXdwEACayb7YY7hq/9BW0bNUNUCKnZa8fHt4dAIpYb0XT7kB9+ne+5yKtrMKc39mf1yR0kNg0hnSUECuG@postgresql.org X-Gm-Message-State: AOJu0YzrJC61Drs3W7YrjpRj3UF8Eez4967LV2kYvU5NQRv0ghX4r1ER VKzzi9ksAGfUymH54TcnEdCV5CwN2Y4oore8zjb3UaZPhIjN/GerRTP4lILVDY2Hx24= X-Gm-Gg: AfdE7ck8/Ho9rQWWUzOMYzpxO9ucN4v+ce7upUOUktIZlRN+7C4/fu9nHRJusCPtgsJ Uvm2+ovvGUJWG2gKp5XlvL6H46Z+Zxt4ya6HhNbERpvh1EzgCRgdmJrrgPQA7IOTVKysazov/1M FBs2J0xSWjOZou//im1yDKQUtxxG9dS15Vtry9cZjH6/VdEIeC2urtbNsgYAF1/ApYQBq5Ec7C+ V/X7Idvzna2G7FcxPIoY+0UpCFfQC/k2fHqnhV/WxSvKxLqdcuuzzul3S487h88tkBoTj+Rq/p2 SWm0UxtkJ52STaukQXPZEDTBQJTh7G1B3EKvLxkYMYDJLFkED8Xw8kpndxAe+Yp+5Wnun/C4ucl jeXCGs1JFZ8IYIa46d7qavY9qko8tX7UtSK58b1ixLTNxeHDZLQuMZseWUlF0xSQK/qrn2xkKjO 3LabLIvcmPNmTiATKp2E2X/NHBYeMhDn+GFcXTqwojApoaJjsoOQhdQpM= X-Received: by 2002:a05:600d:6447:10b0:493:d078:796 with SMTP id 5b1f17b1804b1-493f2592f10mr19452095e9.17.1783666319478; Thu, 09 Jul 2026 23:51:59 -0700 (PDT) Received: from laurenz.albe-K4N0CV00F97414D ([2001:871:270:b5dd:d75a:6339:d2c8:1d69]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-47aa039b126sm56752961f8f.24.2026.07.09.23.51.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 09 Jul 2026 23:51:58 -0700 (PDT) Message-ID: <79b63e772179abb4f5ad749d9e067d48a5537599.camel@cybertec.at> Subject: Re: Adding a stored generated column without long-lived locks From: Laurenz Albe To: Alberto Piai , pgsql-hackers@postgresql.org Cc: =?ISO-8859-1?Q?=C1lvaro?= Herrera Date: Fri, 10 Jul 2026 08:51:58 +0200 In-Reply-To: References: <3af338791e8ab27c4a7efbc4217c97162de76bc8.camel@cybertec.at> <2eb68e74ea95fc2f691de7d8cd28448d00d1d216.camel@cybertec.at> Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable User-Agent: Evolution 3.58.3 (3.58.3-1.fc43) MIME-Version: 1.0 List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk On Fri, 2026-07-10 at 02:10 +0200, Alberto Piai wrote: > > The syntax you ended up with (ADD GENERATED ALWAYS STORED USING CONSTRA= INT) > > is ugly as hell. I see your point in having ALWAYS and STORED, but per= haps > > ADD GENERATED ALWAYS USING CONSTRAINT ... STORED would be better, as it= is > > syntactically more like GENERATED ALWAYS AS (...) STORED, which would m= ake > > it easier to remember. > >=20 > > Or perhaps ADD GENERATED USING CONSTRAINT would be enough, since ALWAYS= and > > STORED are the only possible choice anyway. I am a bit uncertain on wh= at > > is best here. >=20 > Your point about moving STORED at the end is good, I feel bad about > dropping it completely though, because in the ADD COLUMN subcommand the > default is VIRTUAL when omitted. Maybe we could drop the ALWAYS, since > that is only relevant when talking about identity columns. That would > result in: >=20 > ... ADD GENERATED USING CONSTRAINT constr_name STORED >=20 > Another option would be to be consistent with SET|DROP EXPRESSION: >=20 > ... ADD EXPRESSION USING CONSTRAINT constr_name STORED >=20 > I would not omit STORED here either, because SET|DROP EXPRESSION act on > what's already a generated column (so they don't need to specify the > type at all), but this one does need to know. >=20 > The ALTER TABLE page of the manual already groups these 3 together: >=20 > ADD GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY > SET GENERATED { ALWAYS | BY DEFAULT } > DROP IDENTITY [ IF EXISTS ] >=20 > These forms change whether a column is an identity column... >=20 > Using EXPRESSION for this new command would result in a similar group: >=20 > ADD EXPRESSION USING CONSTRAINT constr_name STORED > SET EXPRESSION AS > DROP EXPRESSION [ IF EXISTS ] >=20 > the three forms change whether a column is a generated column or change > the generator expression. The first and the third only work with stored > generated columns. >=20 > But at this point we are just debating whether to say GENERATED or > EXPRESSION. Personally I find both OK with a slight preference for the > latter. I take your point about keeping the STORED. If VIRTUAL is the default when creating a generated column, then keeping the explicit STORED makes sense. About GENERATED vs. EXPRESSION: my feelings go the other way. If I read ALTER TABLE tab ALTER col ADD EXPRESSION ... then it is not clear to me *what* expression is added. After all, there are not only generation expressions, but also column default expressions, and I think it is good to make clear that this is about generated columns. I think I understand your preference: after all, we are not ADDing a GENERATED column, but only an EXPRESSION that turns a regular column into a generated column. Perhaps I can convince you by showing the similar ALTER TABLE tab ALTER col ADD GENERATED ALWAYS AS IDENTITY; Here, too, we are not adding a new generated column, only turning a regular column into a generated one. I think it is good to stay as close to that already established syntax as possible. > > >=20 > > There is a weird asymmetry in that the order in which you write the che= ck > > constraint matters: > >=20 > > CREATE TABLE tab (a integer PRIMARY KEY, b integer NOT NULL); > > INSERT INTO tab VALUES (1, 2); > >=20 > > -- this fails > > ALTER TABLE tab ADD CONSTRAINT c CHECK (2 * a =3D b); > > ALTER TABLE tab ALTER b ADD GENERATED ALWAYS STORED USING CONSTRAINT = c; > > ERROR: cannot convert a column into a stored generated column withou= t a constraint to prove that the values are consistent > > DETAIL: could not find a valid constraint "c" CHECK ("b" =3D expr) o= r CHECK ("b" IS NOT DISTINCT FROM (expr)) > >=20 > > -- but this works > > ALTER TABLE tab DROP CONSTRAINT c; > > ALTER TABLE tab ADD CONSTRAINT c CHECK (b =3D 2 * a); > > ALTER TABLE tab ALTER b ADD GENERATED ALWAYS STORED USING CONSTRAINT = c; > >=20 > > I think that both variants should be accepted, but I am not certain. >=20 > My original approach here was to be as literal as possible with the > shape of the constraint required to perform this operation. It is after > all an extremely ad-hoc constraint, added just for the purpose of doing > this online migration. But just checking both directions for the > equalities seems simple enough, I'll try to do this in the next version > of the patch. Right; I thought that it was no big deal. And this would be the only case in the PostgreSQL SQL dialect that I know where the order of the operands for an equality condition matters. > > > + /* > > > + * Ignore the column's own default expression= . We > > > + * handle sequences above, and for a column w= hich is > > > + * already a generated column we should never= get > > > + * here. > > > + */ > >=20 > > It's not strictly required, but it would be great if you could run pgin= dent > > to get comments and other parts of the code formatted properly. >=20 > This is strange, I did run pgindent. Which part seems formatted > incorrectly to you? I'll try to see if we're hitting some edge case that > makes pgindent skip the code. I had not tested it, but the lines seemed to be shorter than the 80 columns to which pgindent formats comments. If you already ran pgindent, please ignore my comment. Yours, Laurenz Albe