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 1weYlZ-004rxi-10 for pgsql-hackers@arkaria.postgresql.org; Tue, 30 Jun 2026 13:44:45 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1weYlX-008VGP-2D for pgsql-hackers@arkaria.postgresql.org; Tue, 30 Jun 2026 13:44:43 +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 1weYlW-008VGG-39 for pgsql-hackers@lists.postgresql.org; Tue, 30 Jun 2026 13:44:43 +0000 Received: from mail-wm1-x335.google.com ([2a00:1450:4864:20::335]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1weYlS-00000000vHu-0npD for pgsql-hackers@postgresql.org; Tue, 30 Jun 2026 13:44:42 +0000 Received: by mail-wm1-x335.google.com with SMTP id 5b1f17b1804b1-493b68b4643so9606735e9.0 for ; Tue, 30 Jun 2026 06:44:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1782827075; x=1783431875; darn=postgresql.org; h=in-reply-to:content-transfer-encoding:content-disposition :mime-version:references:message-id:subject:cc:to:from:date:from:to :cc:subject:date:message-id:reply-to; bh=o79TVpi2/mqDaRHLU6w1nKmRSCIZ9APXOdSrC6JsUHc=; b=nwIRIi0DWoaHGqc1uV34zVetUgJuEAsFqORjTqB2kVFR38ZrCfVj5FY56WEbxTBZr+ tXmWkXkww+cn0uJ9muKrDg8dBq/n54T6flHTuf3eUZFm9SeeTGRuFeuCg9hM4xF50ez7 ieHqk8dVeFR7lqwyLFrOwEqTG6gxf9UHRmBROfyd88MKCx1BGQk8bEMoRxFhbif1G55C /s7Y0EXUNaql89WV34vjlMFsn60Sh8X6w9N91wA8W8ze5cfb7WWivjKD4JRuKODQFQVj F26J8uE0iCRt9V6yWROOlu1LtRz5WvnaaZk85F99CWQk0Ni1ph7iQO2HvPAtO9dgZMnL PH1w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782827075; x=1783431875; h=in-reply-to:content-transfer-encoding:content-disposition :mime-version:references:message-id:subject:cc:to:from:date:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=o79TVpi2/mqDaRHLU6w1nKmRSCIZ9APXOdSrC6JsUHc=; b=CjPkXS8W4GchgownL87gDxRihp9+iezcRkJQDnWzoJ06Ks14YxMr7rExC6VbfQStTF SpcfcPbHr+zNqbFTOBUl8bWjBDxbkLQsnkqgjsLdjIgq4BQl6qHKXIhgtGSz6TLM7y4q GPLeCKIJIpVg/Tb/b3xLUShS4astKwsFQ5oAR+2wWx6rPLaXddAAddYA/SPwxICtOtDb A1k/UvQh5u2HefH9fyLghKzL2dWw51NU30c/rCh0vzXpmFbqXFBIRkRYJX99uYAmXJLf bG6sTY0xwx8p2rmpnzRLCZrq2E5Nd7m9bUWpcGOHX6bVMJvRYJ2PcQK2OSBSCY0SAAmC BG6w== X-Forwarded-Encrypted: i=1; AFNElJ+LCqiWRKvoYa97WRr1px1w3Bnjx3n36HH6gixW7hLtJkakmoyDlKHywyaHibPEXGcpPaqXQezLJg1cQEUo@postgresql.org X-Gm-Message-State: AOJu0YyHbEcv9er9e8ocKdM0TbASrszzSyJdtrcP+AHl5Awnj2z8CAMv rolxULIlyp3e0ZWe9cv+/ZzUwSnvzBieFwc9dolkYp/9/O+lFac+mekH X-Gm-Gg: AfdE7ckux2vjr6BJmKrFut717hqzyAGWu0EWjrn0vJ95LPBqZYK2/lPRpZqWq096O+E elgg6FFzu6wE5CvPTjV1keCA6WAzzFfgFOysd0M4TLcZNx1s6x1qt17CXDxnXsd2SjsNjIgw5kz ZObxM3bpSeSVFq4YZmiP5UUuwe8vlFK2ABxPLVzodHLPdB4BvgwEFPh0tQpsxmPD0/oymnAN3ZP yiDzGnWgzhs8uKdY7ADr6bhGYCCgJX7pWPykyeLyOS+fFTxcbSEADl0qe9X61Hy/w5mbTXiLp3u d14BsnaOYUxyDZVq7Pw9HT3tYVj94VOIW32civvJRznXh//Lkfmtt4oXibTRzWVMonfob4qx146 YNY6UCHYhFVhhEActu7F+YDZBMrjDTjQFcF3kx2MlU1pVGeINeGENg6ltGYNx/L9KwYTXIV840f Hv8JkDT8Cd0DpGJ36Rp0okA2sQBsCiaAo= X-Received: by 2002:a05:600c:608c:b0:493:bb0:3b43 with SMTP id 5b1f17b1804b1-493b827fc7bmr56690155e9.2.1782827074791; Tue, 30 Jun 2026 06:44:34 -0700 (PDT) Received: from localhost ([2a02:169:191:0:8159:833e:359:9dc7]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-493b8cd8bccsm76301125e9.8.2026.06.30.06.44.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 30 Jun 2026 06:44:33 -0700 (PDT) Date: Tue, 30 Jun 2026 15:44:28 +0200 From: Alberto Piai To: Laurenz Albe , Alberto Piai , pgsql-hackers@postgresql.org Cc: =?utf-8?Q?=C3=81lvaro?= Herrera Subject: Re: Adding a stored generated column without long-lived locks Message-ID: X-Mailer: aerc 0.21.0 References: <3af338791e8ab27c4a7efbc4217c97162de76bc8.camel@cybertec.at> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="vwkr5fjnqh6lckov" Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: <3af338791e8ab27c4a7efbc4217c97162de76bc8.camel@cybertec.at> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --vwkr5fjnqh6lckov Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: 8bit On Mon Jun 15, 2026 at 8:38 PM CEST, Laurenz Albe wrote: > On Wed, 2026-05-27 at 19:43 +0200, Alberto Piai wrote: >> Design iteration 2 >> ------------------ > > I like this proposal. It avoids the question "to rewrite or not to > rewrite" by just outright failing if there is no suitable constraint. > > The idea to avoid the problem with NULL by forcing IS NOT DISTINCT FROM > in the constraint is a good solution. Perhaps you could also allow > the equality operator if the column in question is defined NOT NULL. In the attached v5 patch I've implemented this design, and went one step further (let me know what you think). While discussing this with my colleagues at work, the question came up (thanks, Philip!): now that we mention the constraint explicitly, what's the point of repeating the expression too? The constraint already defines an equality to an expression. I think this is a very good point, and it removes one further way in which the operation could fail, so I went ahead and changed the command to not mention the expression. It takes the expression defined in the constraint and uses _that_ as the generator expression of the column. Design iteration 3 ------------------ Syntax: ALTER TABLE t ALTER COLUMN c ADD GENERATED ALWAYS STORED USING CONSTRAINT check_name check_name must be a valid constraint of a specific shape. If the column is nullable: CHECK (c IS NOT DISTINCT FROM expr) If the column is NOT NULL, either of the following is acceptable: CHECK (c = expr) CHECK (c IS NOT DISTINCT FROM expr) The column is then changed to be a stored generated column, with the "expr" from the constraint as its generator expression. >> Any thoughts about this? > > Yes. I think that you should not drop the constraint. That's what I'd > expect, similar to how we don't drop the check constraint that allows > to skip the table scan in ALTER TABLE ... ALTER COLUMN ... SET NOT > NULL or ALTER TABLE ... ATTACK PARTITION. > > I feel that automatically dropping the constraint is a bit too much > black magic, but it is more a feeling than a conviction. I don't have a strong opinion on whether to cleanup or not, I'll gladly take your input. This version of the patch does not drop the constraint anymore. This version addresses your inputs from the last review: - I added documentation for the new alter table form to alter_table.sgml - Tab completion for psql is there now - The missing error conditions in case of an identity column or sequences are now handled, more about this in the next section. Failure conditions ------------------ There's quite a few invalid states that cannot be reached via CREATE TABLE and should not be reachable via ALTER TABLE either. The following are detected and fail the operation: - the column is already a generated column - the column is an identity column - the column is referenced by a sequence (it is most likely a serial column) - the column is referenced by another column's default expression - the column references another generated column - the column is referenced in a partition key, either directly or through a whole row expression - the new default expression is not immutable Additionally, we of course bail if the constraint is not found, not valid, not enforced or doesn't match the specific structure we need. Another case I considered is the column being referenced in the body of a pre-parsed function (BEGIN ATOMIC SQL functions). In this case though, it seems to me that we don't need to fail here: we are not altering the type of the column, and when reading a stored generated column there's no expression replacement happening (as it does when reading virtual columns). Partitioning/inheritance is supported only on the whole hierarchy at once (see 8bf6ec3ba3a44448817af47a080587f3b71bee08). Trying to change the column at only one level will fail, as well as any of the subtrees. I also added a test to explictily check that we're not accidentally enqueuing a table scan for verification in phase 3, as avoiding this kind of work is the whole point of the command. Logical replication ------------------- The interaction with logical replication is tricky, since a publication can have the option to publish generated columns or not (which is the default). When not publishing stored generated columns, inserts or updates would be replicated while backfilling, and would then suddenly stop when the column is turned into a stored generated column. One way to avoid this is to set up triggers on the subscriber too, before altering the column on the publisher. This way updates and inserts would not lose the column's value on the subscriber, which can then be migrated by using the new alter table command. When publishing stored generated columns instead, it is not possible to have the same column be stored generated on both the publisher and the subscriber (see Table 29.2 in section 29.6. Generated Column Replication). The only supported configuration has a regular column on the side of the subscriber. (Note that this is not specific to this new command.) This makes this scenario a lot easier: the column is migrated on the publisher only, and the subscriber won't lose any value. To test these two scenarios, I wrote TAP tests for the subscription suite. However, I'm inclined to not add them to the test suite. I have attached them to this email separately. Other changes since v4 ---------------------- I have changed phase 2 to be ran at AT_PASS_ADD_OTHERCONSTR, before it was at AT_PASS_SET_EXPRESSION. The reason to do it there was to reuse the cleanup steps in ATPostAlterTypeCleanup when a table rewrite did happen. But since now never rewrite, this is not necessary anymore. Looking forward to your thoughts on this! Alberto -- Alberto Piai Sensational AG Zürich, Switzerland --vwkr5fjnqh6lckov Content-Type: text/plain; charset=utf-8 Content-Disposition: attachment; filename="v5-0001-Support-changing-a-column-into-a-stored-generated.patch"