]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Update JIT tuple deforming code for virtual generated columns
authorDavid Rowley <drowley@postgresql.org>
Fri, 19 Jun 2026 03:26:51 +0000 (15:26 +1200)
committerDavid Rowley <drowley@postgresql.org>
Fri, 19 Jun 2026 03:26:51 +0000 (15:26 +1200)
The JIT deforming code contains an optimization that determines which
columns are guaranteed to exist in the tuple.  That's used to allow
skipping of reading the tuple's natts when the code only needs to deform
attributes that are guaranteed to always exist in all tuples.  83ea6c540
missed updating this code to account for VIRTUAL generated columns.
These are stored as NULLs in the tuple, but may be defined as NOT NULL.
This could result in the code thinking more columns are guaranteed to
exist than actually do.

Author: David Rowley <dgrowleyml@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Backpatch-through: 18
Discussion: https://postgr.es/m/1151393.1781734980@sss.pgh.pa.us

src/backend/jit/llvm/llvmjit_deform.c
src/test/regress/expected/generated_virtual.out
src/test/regress/sql/generated_virtual.sql

index 798c1ce4ed2f973e872990988b08188775ad32c2..9cff932d058b42b927723700e4648d34ae1324c4 100644 (file)
@@ -105,27 +105,32 @@ slot_compile_deform(LLVMJitContext *context, TupleDesc desc,
        funcname = llvm_expand_funcname(context, "deform");
 
        /*
-        * Check which columns have to exist, so we don't have to check the row's
-        * natts unnecessarily.
+        * Check which columns have to exist in all tuples, so we don't have to
+        * check the row's natts unnecessarily.
         */
        for (attnum = 0; attnum < desc->natts; attnum++)
        {
-               CompactAttribute *att = TupleDescCompactAttr(desc, attnum);
+               CompactAttribute *catt = TupleDescCompactAttr(desc, attnum);
+               Form_pg_attribute attr = TupleDescAttr(desc, attnum);
 
                /*
                 * If the column is declared NOT NULL then it must be present in every
                 * tuple, unless there's a "missing" entry that could provide a
                 * non-NULL value for it. That in turn guarantees that the NULL bitmap
                 * - if there are any NULLable columns - is at least long enough to
-                * cover columns up to attnum.
+                * cover columns up to attnum.  We treat virtual generated columns
+                * similar to atthasmissing columns, as these columns could either not
+                * be represented in the tuple or could have the column represented as
+                * a NULL in the null bitmap.
                 *
                 * Be paranoid and also check !attisdropped, even though the
                 * combination of attisdropped && attnotnull combination shouldn't
                 * exist.
                 */
-               if (att->attnullability == ATTNULLABLE_VALID &&
-                       !att->atthasmissing &&
-                       !att->attisdropped)
+               if (catt->attnullability == ATTNULLABLE_VALID &&
+                       !catt->atthasmissing &&
+                       !catt->attisdropped &&
+                       attr->attgenerated != ATTRIBUTE_GENERATED_VIRTUAL)
                        guaranteed_column_number = attnum;
        }
 
@@ -394,6 +399,8 @@ slot_compile_deform(LLVMJitContext *context, TupleDesc desc,
        for (attnum = 0; attnum < natts; attnum++)
        {
                CompactAttribute *att = TupleDescCompactAttr(desc, attnum);
+               Form_pg_attribute attr = TupleDescAttr(desc, attnum);
+
                LLVMValueRef v_incby;
                int                     alignto = att->attalignby;
                LLVMValueRef l_attno = l_int16_const(lc, attnum);
@@ -436,9 +443,12 @@ slot_compile_deform(LLVMJitContext *context, TupleDesc desc,
                /*
                 * Check for nulls if necessary. No need to take missing attributes
                 * into account, because if they're present the heaptuple's natts
-                * would have indicated that a slot_getmissingattrs() is needed.
+                * would have indicated that a slot_getmissingattrs() is needed. When
+                * present in the tuple, virtual generated columns are always stored
+                * as NULL, so we must always perform NULL checks for these.
                 */
-               if (att->attnullability != ATTNULLABLE_VALID)
+               if (att->attnullability != ATTNULLABLE_VALID ||
+                       attr->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
                {
                        LLVMBasicBlockRef b_ifnotnull;
                        LLVMBasicBlockRef b_ifnull;
@@ -616,12 +626,14 @@ slot_compile_deform(LLVMJitContext *context, TupleDesc desc,
                        known_alignment += att->attlen;
                }
                else if (att->attnullability == ATTNULLABLE_VALID &&
+                                attr->attgenerated != ATTRIBUTE_GENERATED_VIRTUAL &&
                                 (att->attlen % alignto) == 0)
                {
                        /*
-                        * After a NOT NULL fixed-width column with a length that is a
-                        * multiple of its alignment requirement, we know the following
-                        * column is aligned to at least the current column's alignment.
+                        * After a NOT NULL (and not virtual generated) fixed-width column
+                        * with a length that is a multiple of its alignment requirement,
+                        * we know the following column is aligned to at least the current
+                        * column's alignment.
                         */
                        Assert(att->attlen > 0);
                        known_alignment = alignto;
index 171d68ba2e251db31f955100b0568ee17637e810..03698488907d3e1768991fb5629aa766f36c6e9d 100644 (file)
@@ -700,6 +700,48 @@ ERROR:  null value in column "b" of relation "gtest21b" violates not-null constr
 DETAIL:  Failing row contains (null, virtual).
 ALTER TABLE gtest21b ALTER COLUMN b DROP NOT NULL;
 INSERT INTO gtest21b (a) VALUES (0);  -- ok now
+-- virtual generated columns are not physically stored, even when not null
+CREATE TABLE gtest21c (a int NOT NULL, b int GENERATED ALWAYS AS (a * 2) VIRTUAL NOT NULL, c int NOT NULL);
+INSERT INTO gtest21c (a, c) VALUES (10, 42);
+SELECT a, b, c FROM gtest21c;
+ a  | b  | c  
+----+----+----
+ 10 | 20 | 42
+(1 row)
+
+DROP TABLE gtest21c;
+-- try adding a virtual generated column to an existing table with tuples,
+-- then try adding an atthasmissing column before adding a normal nullable
+-- column.
+CREATE TABLE gtest21d (a int NOT NULL);
+INSERT INTO gtest21d (a) VALUES(10);
+ALTER TABLE gtest21d ADD COLUMN b INT GENERATED ALWAYS AS (a * 10) VIRTUAL NOT NULL;
+SELECT * FROM gtest21d ORDER BY a;
+ a  |  b  
+----+-----
+ 10 | 100
+(1 row)
+
+INSERT INTO gtest21d (a) VALUES(20);
+ALTER TABLE gtest21d ADD COLUMN c INT NOT NULL DEFAULT 1234;
+SELECT * FROM gtest21d ORDER BY a;
+ a  |  b  |  c   
+----+-----+------
+ 10 | 100 | 1234
+ 20 | 200 | 1234
+(2 rows)
+
+ALTER TABLE gtest21d ADD COLUMN d INT;
+INSERT INTO gtest21d (a, c, d) VALUES(30, 12345, 100);
+SELECT * FROM gtest21d ORDER BY a;
+ a  |  b  |   c   |  d  
+----+-----+-------+-----
+ 10 | 100 |  1234 |    
+ 20 | 200 |  1234 |    
+ 30 | 300 | 12345 | 100
+(3 rows)
+
+DROP TABLE gtest21d;
 -- not-null constraint with partitioned table
 CREATE TABLE gtestnn_parent (
     f1 int,
index 0ba8c626047c1a1fd44ccf79d0e943089c0609fa..6054a8c712556f5bcc4bb48fa57cf12eb82d921a 100644 (file)
@@ -366,6 +366,27 @@ INSERT INTO gtest21b (a) VALUES (NULL);  -- error
 ALTER TABLE gtest21b ALTER COLUMN b DROP NOT NULL;
 INSERT INTO gtest21b (a) VALUES (0);  -- ok now
 
+-- virtual generated columns are not physically stored, even when not null
+CREATE TABLE gtest21c (a int NOT NULL, b int GENERATED ALWAYS AS (a * 2) VIRTUAL NOT NULL, c int NOT NULL);
+INSERT INTO gtest21c (a, c) VALUES (10, 42);
+SELECT a, b, c FROM gtest21c;
+DROP TABLE gtest21c;
+
+-- try adding a virtual generated column to an existing table with tuples,
+-- then try adding an atthasmissing column before adding a normal nullable
+-- column.
+CREATE TABLE gtest21d (a int NOT NULL);
+INSERT INTO gtest21d (a) VALUES(10);
+ALTER TABLE gtest21d ADD COLUMN b INT GENERATED ALWAYS AS (a * 10) VIRTUAL NOT NULL;
+SELECT * FROM gtest21d ORDER BY a;
+INSERT INTO gtest21d (a) VALUES(20);
+ALTER TABLE gtest21d ADD COLUMN c INT NOT NULL DEFAULT 1234;
+SELECT * FROM gtest21d ORDER BY a;
+ALTER TABLE gtest21d ADD COLUMN d INT;
+INSERT INTO gtest21d (a, c, d) VALUES(30, 12345, 100);
+SELECT * FROM gtest21d ORDER BY a;
+DROP TABLE gtest21d;
+
 -- not-null constraint with partitioned table
 CREATE TABLE gtestnn_parent (
     f1 int,