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 1wSIJ8-0035m0-0V for pgsql-hackers@arkaria.postgresql.org; Wed, 27 May 2026 17:44:42 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wSIJ6-008yUJ-0K for pgsql-hackers@arkaria.postgresql.org; Wed, 27 May 2026 17:44:41 +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.96) (envelope-from ) id 1wSIIV-008uZY-2m for pgsql-hackers@lists.postgresql.org; Wed, 27 May 2026 17:44:04 +0000 Received: from mail-wr1-x42c.google.com ([2a00:1450:4864:20::42c]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wSIIT-00000001jCr-0KW2 for pgsql-hackers@postgresql.org; Wed, 27 May 2026 17:44:02 +0000 Received: by mail-wr1-x42c.google.com with SMTP id ffacd0b85a97d-45ee1a56328so123931f8f.3 for ; Wed, 27 May 2026 10:44:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779903835; x=1780508635; darn=postgresql.org; h=in-reply-to:references:mime-version:content-transfer-encoding:to :from:cc:subject:message-id:date:from:to:cc:subject:date:message-id :reply-to; bh=SBYOW44QEd4yYO8qJ0qNKtYXZh2xFsDutpMkwtVYkAU=; b=Kab7lDSCPmaWNNzeDJzIbESftJsOsJLTzY4blWziKo84kp4dXKZRAuPnf1Xwiqy9X/ idZCx8TAPXOnD9/lXhNcWy+zRHZPjmDaiwFeDA16YhAfwhzURrAnmaGbyOnh7H3Is06F Ecjz8dCj4NztsdZ6M5AL9GH0hW/XzczTwcY8pVIbZ0rZKYsYDxOCWSDizZi0no0jvzlv 1Yn7x2S4ekY4HgBJ3BJYBwNtPMWlYPzuK6I/Eh4HnaRH7KhtmJiSFlk56F+hPBz0frKe 3sPK3GpGfN407KCDJSzh/qsTp4J4JYHfzBwHSHhVnxPalcuezL7B/HKB/JDmcgHRhCVR XHrA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779903835; x=1780508635; h=in-reply-to:references:mime-version:content-transfer-encoding:to :from:cc:subject:message-id:date:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=SBYOW44QEd4yYO8qJ0qNKtYXZh2xFsDutpMkwtVYkAU=; b=VGFUKcO6r1UGLnRXUY/Y5Lj20c5LnOk3lcQgha6LAHyYtWLDD20c0LpwpBAxDZ71uF P0FwaKRqYkw18B8oexK5yDbQeL9JVDgKiAuAJ+TI5400qLxlsRLCHqyDYjBmwAlBNqar faifIbGIUM+qEktgzMVcc2LmKfq/TL6Kq4kmEKTIAgvyPVcuIY+B7pN1bqnR3MUsw5zi xHUmNF4mhiz97E8jQAOfwb4pzjskk7ihFEzUZJFoOT81xonAlupb3OGH/ZenmqvKpQPz A0TQtUK6PoKkw/hrlx9Ka08lwIrC5Aqdawwmgz7kPeFNj292EgPKhgVlNq/pX4y2NQGm aOyg== X-Forwarded-Encrypted: i=1; AFNElJ+SOcfQBwB77MiNagb7FM1DBRrUH1BdP37R63trvga9uIR1MmcLPTHG2Xu50QolF+STxyAED1qQFXstvUhJ@postgresql.org X-Gm-Message-State: AOJu0YxuMSA5WbBsdukKRzgsxp+Cs8leoBUjBFyKquUjyOQ8p5jNq7LD E7NEP87sz0EbZ8j14u0n0km4Xos7YBYJDIXpgdCUkxmyxVnG/eCquM/T X-Gm-Gg: Acq92OHtKS8EICM+gqvPogwRG9HP986zB3aR1e5hPB3xHFBDC+/2Gz8/hABP1TUZFgK XTvRphWb0BBWwPTzgzBKcZvBKCR2NP4h5yVb9PvZtd0GRG3LCd6nuihWyC/pQThGvY/F/Epzk8H ZklqtBdd6GwCwn8OVhok6A5ddi/rnmtcyqPS5hoFZkiUqKoz/HJEKUWgjVcWD2mVgz4s7X7NJ44 rMqF6dMOc7loSCSrLjdP/xjIaPVxxbet1BTxpQ0cUE8agqAdqKA8F1tBVXjr2Fjy45LFIPijRwR 10+9MSxn85btM6lumZ+mK7FzxNu+h5qcclDnmqmAQm4erco2542lOVDOa8Vj5udIw5HbgiQMIou 88U1cMlFIndFNcVyyrAG8Jy+WbTBmnJvQdHyt3MAydlbV7F5cIvdIqwDJs+fpBWeCpxj6t+OCXT R6Fib0wibRy9vO+EMdWZ+T4M4lPLxM6HZMnzY2MJvwYhRFAaA/EIxsMgO3XkF3JDYry/RhEFpvN A== X-Received: by 2002:a05:6000:1448:b0:45e:e3ee:98b1 with SMTP id ffacd0b85a97d-45ee3ee9a8dmr50381f8f.32.1779903834445; Wed, 27 May 2026 10:43:54 -0700 (PDT) Received: from localhost (85-195-242-240.fiber7.init7.net. [85.195.242.240]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-45edb5b2bbfsm7923490f8f.32.2026.05.27.10.43.53 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Wed, 27 May 2026 10:43:54 -0700 (PDT) Content-Type: text/plain; charset=UTF-8 Date: Wed, 27 May 2026 19:43:53 +0200 Message-Id: Subject: Re: Adding a stored generated column without long-lived locks Cc: =?utf-8?q?=C3=81lvaro_Herrera?= , "Laurenz Albe" From: "Alberto Piai" To: "Alberto Piai" , Content-Transfer-Encoding: quoted-printable Mime-Version: 1.0 X-Mailer: aerc 0.21.0 References: In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Hi, On Fri May 15, 2026 at 12:46 AM CEST, Alberto Piai wrote: > On Fri Apr 24, 2026 at 2:10 AM PDT, Alberto Piai wrote: >> On Tue Apr 7, 2026 at 5:02 PM +08, Alberto Piai wrote: >>> On Tue Mar 17, 2026 at 5:31 PM +07, Alberto Piai wrote: >>> >>>> I recently needed to add a stored generated column to a table of >>>> nontrivial size, and realized that currently there is no way to do >>>> that without rewriting the table under an AccessExclusiveLock. here's a not-so-brief summary of the conversations around this topic at pgconf.dev, and a new proposal at the end. I had the chance to bring this up with other attendees, and many recognized the use case as a useful one, addressing a real operational issue. In particular, I had great feedback from Sta=C5=A1 Kotarac Gu=C4=8Dek, who = pointed out a major flaw in my current proposal: a constraint of the form CHECK (c =3D expr) would not work correctly when expr evaluates to null for some rows. Thank you Sta=C5=A1, in the next iteration I will change the constraint to use IS NOT DISTINCT FROM, instead. I briefly mentioned this topic to Tom Lane, who quickly replied with the question: should this not fail when it can't use the constraint, instead of overwriting the contents of the column? Thanks Tom, I will get to this later in this mail. I had registered this patch for the in-person commitfest at pgconf.dev, and =C3=81lvaro Herrera picked it up for review. Thank you =C3=81lvaro, and= thank you Peter for organizing the event. We managed to find some time on the very last day of the conference, and went through the current design and code. The open items (which I will address in the next iteration of this patch) are: * missing user documentation I will work on this next. I think it's a good way to explain the feature even early during development. I just didn't want to do it _too_ early, without having had any feedback. * try to minimize command counter increments There might be one call to CommandCounterIncrement() which is not necessary, I'll try remove it. * comment on why it is necessary to clear missing values when rewriting the table ATExecAlterColumnType() and ATExecSetExpression() both do this explicitly when requesting a table rewrite. I'll extend the comment, and also look into whether this is something that should be done any time a table rewrite happens. In that case, it might be worth moving this into the rewriting code rather than having each caller do it. * interactions with other subcommands in the same alter table statement My reasoning regarding this was: if I do this in AT_PASS_SET_EXPRESSION, it should be safe. I will invest some more time into this and add tests, too. We also looked at the overall design of the new command, and we agreed that it is a fitting addition to our current SET EXPRESSION and DROP EXPRESSION. Regarding the question of whether it should be SET or ADD, we agreed that ADD (i.e. the current proposal) is clearer, especially for its similarity to ADD GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY. Regarding the question of "should this fail or rewrite the table when a usable constraint isn't found": =C3=81lvaro's suggestion here was to use a more ad-hoc command, meant more specifically for this use case of converting into a stored generated column without rewriting it. If the command would be dedicated specifically to this, it would make sense to have it fail when a usable constraint isn't found. Last but not least, I also discussed this with Laurenz Albe, and he wrote a very useful review in this thread. I will address that separately and reply directly to that mail, but one point I can already merge in this discussion is about the syntax of the command: > 1) The SQL standard knows ALTER TABLE ... ADD ... GENERATED ALWAYS AS (..= .) > and ALTER TABLE ... ALTER ... DROP EXPRESSION, but there is no provisi= on > for ALTER TABLE ... ADD GENERATED ALWAYS AS (...). > So this patch adds non-standard syntax that may one day conflict with > a new version of the standard. I think we can still do it, and the > proposed syntax looks right, but I thought I should mention it. I'd like to take his point, together with the question from Tom and the suggestion by =C3=81lvaro, and make a new proposal for the design of this command. Design iteration 2 ------------------ Syntax: ALTER TABLE t ALTER COLUMN c ADD GENERATED ALWAYS AS (expr) STORED USING CONSTRAINT check_name check_name must be a valid constraint of the form CHECK (c IS NOT DISTINCT FROM (expr)) This fails if: - a check constraint named check_name is not found for table c - the constraint is not valid - the constraint does not match exactly the expr the user intends to use as a stored default expression On success, the table c is now a stored generated column with the given default expression, and the check_name constraint has been removed. This addresses Tom's remark, we can now fail instead of just rewriting the column. It improves slightly upon the issue of a potential conflict with a future edition of the SQL standard, by being more specific. I don't see a way to be completely sure we won't have conflicts. We could improve more by making the syntax more "alien" and very unlinkely to be picked up by the standard, but at a usability cost for Postgres. I'm open to suggestions. It improves upon another question raised by =C3=81lvaro: does the user have to clean up the constraint? In v1 I felt it was better to have the user remove it after the migration. Since here it's explicitly mentioned as the constraint to use to migrate the column, I think it's OK to remove it. We are conceptually moving it from being a constraint to being the new default expression. The implementation should also be simpler, since there will never be a table rewrite. Any thoughts about this? Best regards, Alberto --=20 Alberto Piai Sensational AG Z=C3=BCrich, Switzerland