I am adding some columns to a table, and want a generated column that combines them together that I’ll use for a unique index. When I try to add the column, I get the error ERROR: generation expression is not immutable
.
I followed the solution from this question, and am specifically using CASE
and ||
for string concatenation, which are supposed to be immutable
.
ALTER TABLE tag
ADD COLUMN prefix VARCHAR(4) NOT NULL,
ADD COLUMN middle BIGINT NOT NULL,
ADD COLUMN postfix VARCHAR(4), -- nullable
-- VARCHAR size is 4 prefix + 19 middle + 4 postfix + 2 delimiter
ADD COLUMN tag_id VARCHAR(29) NOT NULL GENERATED ALWAYS AS
(CASE WHEN postfix IS NULL THEN prefix || '-' || middle
ELSE prefix || '-' || middle || '-' || postfix
END
) STORED;
CREATE UNIQUE INDEX unq_tag_tag_id ON tag(tag_id);
In the postgres mailing list, one of the contributors clarifies that:
integer-to-text coercion, […] isn’t necessarily
immutable
However, he doesn’t share an integer-to-text function that is immutable. Does anyone know if one exists?
2
Answers
Test in 14.1 shows that the cause is the implicite conversion of the
bigint
column totext
in the concatenation (even without acase
)An explicite cast to
text
produces no error –middle::text
Marmite Bomber’s answer shows the soluion; let me add an explanation.
There are two concatenation operators for
text
:The first operator concatenates
text
withtext
, the second concatenatestext
with anything else.Let’s examine the volatility of these two functions:
So if you concatenate
text
andbigint
, that operation is notIMMUTABLE
, but casting thebigint
totext
first makes the operationIMMUTABLE
.It is not because of concatenating with integers that this operator is not immutable. But
anynonarray
could be any data type liketimestamp with time zone
, whose string representation depends on the current setting oftimezone
.