I would like to create a function that attempts to cast a group of values into a user-specified type (with a default of text). A very simple function would look like so:
CREATE OR REPLACE FUNCTION cast_to(variable jsonb, key text, target_type anyelement DEFAULT 'TEXT'::regtype) RETURNS anyelement as $$
begin
RETURN CAST(variable->>key AS target_type);
end
$$
language plpgsql;
I have tried the following:
SELECT CAST('foo' AS 'text');
: Gives syntax errorSELECT CAST('foo' AS 'text'::regtype);
: Same error as 1SELECT CAST('foo' AS pg_typeof(null::text));
says that type pg_typeof does not exist
The last attempt was a thought that I could pass in a variable with the target type instead of a text representation. Using the function would then look like SELECT cast_to('text', NULL::text);
.
How to accomplish this or similar functionality?
Edit: As suggested in the comments, I tried to use dynamic SQL. I am not having much luck. I created a very basic case that doesn't use any variables:
CREATE OR REPLACE FUNCTION audit.cast_to() RETURNS text as $$
DECLARE
_sql TEXT := 'SELECT CAST($1 AS $2)';
out TEXT;
begin
EXECUTE _sql
USING 'foo', 'TEXT'
INTO out;
return out;
end
$$
language plpgsql;
That however throws the error:
syntax error at or near "$2"
Actually, it can be done. Even without dynamic SQL. Pretty simple at the surface, too.
CREATE OR REPLACE FUNCTION cast_to(_js jsonb, INOUT _target_type anyelement = NULL::text)
LANGUAGE plpgsql PARALLEL SAFE AS
$func$
BEGIN
SELECT _js ->> pg_typeof(_target_type)::text
INTO _target_type;
END
$func$;
But this minimalist function packs a couple of advanced / tricky details: See the last chapter here for basics:
The final piece of the puzzle is to cast the text
value returned by the ->>
operator to the return type. A simple SQL function is strict about that and does not accept a text
for, say, an integer
. (Nor for an integer
defined by actual input to the polymorphic input parameter. The same goes for a PL/pgSQL function trying to simply RETURN
. An explicit cast would be required.
CAST (expression AS type)
is not a normal function. Nor is the short syntax expression::type
. Those are constructs or syntax elements. You may have noticed that the type name is placed without single quotes, i.e.: as identifier. (Or you have missed that detail, which is the cause for the first 3 syntax errors you report.) And identifiers cannot be parameterized in SQL. That would require dynamic SQL.
However, we can assign the text
result of the expression to a (necessarily typed) variable or parameter to effortlessly achieve the same. The INTO
clause will achieve that. For convenience, I assign to the INOUT
parameter _target_type
directly. So _target_type
serves several purposes:
Defines the polymorphic return type.
Defines the key name for use with the JSON ->> operator. That name is carried by the type of _target_type
and I extract it with pg_typeof()
- which actually returns regtype
, so we need to cast that to text explicitly.
Note that always results in the Postgres standard type name, e.g: 'integer', not 'int' nor 'int4'. If your key names differ from default Postgres type names, you'll have to pass an additional parameter like you had in your original design!
Serves as OUT
parameter (so we do not need to DECLARE
a variable) that can be assigned to.
Has a DEFAULT
value attached to it: = NULL::text
, so the second parameter can be omitted to just return text
.
You tried that in your original, but was off target.DEFAULT 'TEXT'::regtype
You may want to split some of these purposes to multiple parameters / variables.
So it can be done.
Question is: why do you want to do it? Often, there is a faster and less tricky solution around the corner - even if more verbose.