diff --git a/src/backend/jit/llvm/llvmjit_deform.c b/src/backend/jit/llvm/llvmjit_deform.c index 798c1ce4ed2..9cff932d058 100644 --- a/src/backend/jit/llvm/llvmjit_deform.c +++ b/src/backend/jit/llvm/llvmjit_deform.c @@ -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; diff --git a/src/test/regress/expected/generated_virtual.out b/src/test/regress/expected/generated_virtual.out index 171d68ba2e2..03698488907 100644 --- a/src/test/regress/expected/generated_virtual.out +++ b/src/test/regress/expected/generated_virtual.out @@ -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, diff --git a/src/test/regress/sql/generated_virtual.sql b/src/test/regress/sql/generated_virtual.sql index 0ba8c626047..6054a8c7125 100644 --- a/src/test/regress/sql/generated_virtual.sql +++ b/src/test/regress/sql/generated_virtual.sql @@ -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,