In custom functions in PostgreSQL, I can access the NEW
object to retrieve the values of specific columns, for example:
NEW.description
NEW.city
Essentially, this is a static reference at the code-writing stage. But is it possible to achieve the same dynamically, for example, similar to how it’s done in Map structures:
NEW['description']
NEW['name']
In other words, at the code-writing stage, I don’t know which specific column in NEW
I will be referencing, as this is determined dynamically during the function’s execution.
And the same question applies to type casting—can I dynamically cast the retrieved value to a type that is also determined during the function’s execution? For example, INTEGER
, BOOLEAN
, etc. That is, I dynamically obtain these types as TEXT
during the function's execution, perhaps from a query to another configuration table, and I would like to cast the column value from NEW
to such a type.
NEW
is just a record
that you can automagically convert to a jsonb
with to_jsonb(NEW)
and get the exact syntax and behaviour you want:
demo at db<>fiddle
create table test(
a int generated by default as identity primary key
,b boolean,c text,d "char",e "char",f "char");
create table debug_(
payload jsonb
,comment text
,is_valid_as_date boolean
,id int generated by default as identity primary key
,ts timestamp default clock_timestamp());
select setseed(.46);
create function f_test_trigger()returns trigger as $f$
declare new_jsonb jsonb:=to_jsonb(new);
begin
insert into debug_ values
(new_jsonb['c'], 'just one of the columns')
,(new_jsonb['x'], 'this column does not exist')
,(new_jsonb[chr(97+(random()*25)::int)], '1st random lowercase letter key')
,(new_jsonb[chr(97+(random()*25)::int)], '2nd random lowercase letter key');
update debug_
set is_valid_as_date = pg_input_is_valid( payload#>>'{}'--as unquoted text
,'date');
return new;
end $f$ language plpgsql;
create trigger test_trigger after insert on test
for each row execute function f_test_trigger();
insert into test(b,c,d,e,f)values(true,'2024-10-01','d','e','f');
table debug_;
payload | comment | is_valid_as_date | id | ts |
---|---|---|---|---|
"2024-10-01" | just one of the columns | TRUE | 1 | 2025-01-15 18:26:13.079506 |
null | this column does not exist | null | 2 | 2025-01-15 18:26:13.079812 |
null | 1st random lowercase letter key | null | 3 | 2025-01-15 18:26:13.079839 |
"d" | 2nd random lowercase letter key | false | 4 | 2025-01-15 18:26:13.079847 |
As long as the text
value you're dealing with is acceptable as an input for your given target type, your cast()
will work. Recycling an answer from a related thread: in PostgreSQL 16 and above, there's pg_input_is_valid()
that lets you make sure before you attempt to cast()
.
pg_input_is_valid ( string text, type text ) → boolean
Tests whether the given string is valid input for the specified data type, returning true or false. This function will only work as desired if the data type's input function has been updated to report invalid input as a “soft” error. Otherwise, invalid input will abort the transaction, just as if the string had been cast to the type directly.pg_input_is_valid('42', 'integer') → t pg_input_is_valid('42000000000', 'integer') → f pg_input_is_valid('1234.567', 'numeric(7,4)') → f
In PostgreSQL 15 and earlier, you can build your own:
create or replace function is_interpretable_as(arg text, arg_type text)
returns boolean language plpgsql as $$
begin
execute format('select cast(%L as %s)', arg, arg_type);
return true;
exception when others then
return false;
end $$;