Postgres Generation Expression Is Not Immutable
I wanted to have a cool computed field in Postgres, so I can use it from different types of apps (NodeJS and Golang).
The two NodeJS functions I’m moving the DB would be summed up in these methods
static getUserHash (string) {
return crypto
.createHash('md5')
.update(string)
.digest('hex')
}
static getUserString (user) {
return `${user.id}::${user.username}::${user.encPassword}::${user.updatedAt.toISOString()}`
}
At first, I thought I could just do this. It would add a computed, stored field to the user_account table.
ALTER TABLE user_account ADD COLUMN user_hash TEXT GENERATED ALWAYS AS (
md5(id || '::' || username || '::' || enc_password || '::' || updated_at::text)
) STORED;
Nope: Received the error
generation expression is not immutable
Turns out, the computed needs to return the same all the time (no matter where). It didn’t like the updated_at
field, since it’s a timestamptz
and could change based off the TZ. It also is not a fan of fields that can be NULL.
So I ended with this rendition.
ALTER TABLE user_account
ADD COLUMN user_hash TEXT GENERATED ALWAYS AS (
md5(id || '::' || coalesce(enc_password, '') || '::' || date_part('epoch', COALESCE(updated_at at time zone 'UTC', '1990-01-01'))::text)
) STORED;
A slight compromise.
- Using the epoch instead of the ISO 8601 string, which is fine
- Forced the TZ to be UTC
- wrapped NULL-able fields in COALESCE
- Removed the
username
: wasn’t causing a problem, I didn’t need it for this bit