Given this type:
-- Just for testing purposes:
CREATE TYPE testType as (name text)
I can get the value of a field dynamically with this function:
CREATE OR REPLACE FUNCTION get_field(object anyelement, field text) RETURNS text as
$BODY$
DECLARE
value text;
BEGIN
EXECUTE 'SELECT $1."' || field || '"'
USING object
INTO value;
return value;
END;
$BODY$
LANGUAGE plpgsql
Calling get_field('(david)'::testType, 'name') works as expected returning “david”.
But how can I set a value of a field in a composite type? I’ve tried these functions:
CREATE OR REPLACE FUNCTION set_field_try1(object anyelement, field text, value text)
RETURNS anyelement
as
$BODY$
DECLARE
value text;
BEGIN
EXECUTE '$1."' || field || '" := $2'
USING object, value;
return object;
END;
$BODY$
LANGUAGE plpgsql
CREATE OR REPLACE FUNCTION set_field_try2(object anyelement, field text, value text)
RETURNS anyelement
as
$BODY$
DECLARE
value text;
BEGIN
EXECUTE 'SELECT $1 INTO $2."' || field || '"'
USING value, object;
return object;
END;
$BODY$
LANGUAGE plpgsql
CREATE OR REPLACE FUNCTION set_field_try3(object anyelement, field text, value text)
RETURNS anyelement
as
$BODY$
DECLARE
value text;
BEGIN
EXECUTE 'BEGIN $1."' || field || '" := $2; SELECT $1; END;'
INTO object
USING value, object;
return object;
END;
$BODY$
LANGUAGE plpgsql
and some variations.
Calling set_field_tryX doesn’t work. I always get “ERROR: syntax error at or near…”.
How can I accomplish this?
Notes:
- The parameter is
anyelementand the field can be any field in the composite type. I can’t just use object.name. - I’m concerned about SQL injection. Any advice in this would be appreciated but it is not my question.
Faster with
hstoreSince Postgres 9.0, with the additional module
hstoreinstalled in your database there is a very simple and fast solution with the#=operator that …To install the module:
Examples:
Values have to be cast to
textand back, obviously.Example plpgsql functions with more details:
Now works with
json/jsonb, too!There are similar solutions with
json(pg 9.3+) orjsonb(pg 9.4+)The functionality was undocumented, but it’s official since Postgres 13. The manual:
So you can take any existing row and fill arbitrary fields (overwriting what’s in it).
Major advantages of
jsonvshstore:Minor disadvantage: a bit slower.
See @Geir’s added answer for details.
Without
hstoreandjsonIf you are on an older version or cannot install the additional module
hstoreor cannot assume it’s installed, here is an improved version of what I posted previously. Still slower than thehstoreoperator, though:Call:
Notes
An explicit cast of the value
_valto the target data type is not necessary, a string literal in the dynamic query would be coerced automatically, obviating the subquery onpg_type. But I took it one step further:Replace
quote_literal(_val)with direct value insertion via theUSINGclause. Saves one function call and two casts, and is safer anyway.textis coerced to the target type automatically in modern PostgreSQL. (Did not test with versions before 9.1.)array_to_string(ARRAY())is faster thanstring_agg().No variables needed, no
DECLARE. Fewer assignments.No subquery in the dynamic SQL.
($1).fieldis faster.pg_typeof(_comp_val)::text::regclassdoes the same as
(SELECT typrelid FROM pg_catalog.pg_type WHERE oid = pg_typeof($1)::oid)for valid composite types, just faster.
This last modification is built on the assumption that
pg_type.typnameis always identical to the associatedpg_class.relnamefor registered composite types, and the double cast can replace the subquery. I ran this test in a big database to verify, and it came up empty as expected:INOUTparameter obviates the need for an explicitRETURN. This is just a notational shortcut. Pavel won’t like it, he prefers an explicitRETURNstatement …Everything put together this is twice as fast as the previous version.
Original (outdated) answer:
The result is a version that’s ~ 2.25 times faster. But I probably couldn’t have done it without building on Pavel’s second version.
In addition, this version avoids most of the casting to text and back by doing everything within a single query, so it should be much less error prone.
Tested with PostgreSQL 9.0 and 9.1.