On 26th of September, Peter Eisentraut committed patch which adds:
ADD ALTER TYPE ... ADD/DROP/ALTER/RENAME ATTRIBUTE LIKE WITH TABLES, this also requires allowing the existence OF composite types WITH zero attributes. reviewed BY KaiGai Kohei
Custom types, do not get much coverage, but I personally like them. I much prefer to use them for SRF functions (than multiple OUT parameters).
The problem was that changing these types was never easy. you had to drop the type, and create it again.
Now, it's no longer the case – you can “easily" modify them. Why “easily" and not easily? Well, changing this datatype still required function recompilation, so it's not solving all problems. But it's a step in right direction. And besides – if you are changing definition of recordset returned from function – you're likely changing the function itself anyway.
So, let's see how it works:
$ CREATE TYPE test AS ( x int4, y int4 ); $ \d test Composite TYPE "public.test" COLUMN | TYPE --------+--------- x | INTEGER y | INTEGER
Now, we can:
$ ALTER TYPE test ALTER ATTRIBUTE x TYPE int8; ALTER TYPE $ ALTER TYPE test DROP ATTRIBUTE y; ALTER TYPE $ ALTER TYPE test ADD ATTRIBUTE z INT8; ALTER TYPE $ ALTER TYPE test RENAME ATTRIBUTE x TO new_x; ALTER TYPE \d test Composite TYPE "public.test" COLUMN | TYPE --------+-------- new_x | BIGINT z | BIGINT
So, how does it influence the functions?
Let's test.
$ CREATE FUNCTION testit() RETURNS setof test AS $$ DECLARE reply test; BEGIN reply.new_x := 123; reply.z := 456; RETURN NEXT reply; RETURN; END; $$ LANGUAGE plpgsql; CREATE FUNCTION $ SELECT * FROM testit(); new_x | z -------+----- 123 | 456 (1 ROW)
So far so good. So, let's change the type definition, in a way that (at least theoretically) shouldn't matter for the function:
$ ALTER TYPE test ADD COLUMN new_column int4; ALTER TYPE $ SELECT * FROM testit(); ERROR: wrong record TYPE supplied IN RETURN NEXT CONTEXT: PL/pgSQL FUNCTION "testit" line 7 at RETURN NEXT
Oops. But – this happens only because this particular db connection already used the function. If I disconnect, and reconnect again (without any more changes):
$ SELECT * FROM testit(); new_x | z | new_column -------+-----+------------ 123 | 456 | [NULL] (1 ROW)
Alternatively, I could simply reload the same function body with CREATE OR REPLACE:
$ ALTER TYPE test ADD attribute new_column2 int4; ALTER TYPE $ SELECT * FROM testit(); ERROR: wrong record TYPE supplied IN RETURN NEXT CONTEXT: PL/pgSQL FUNCTION "testit" line 7 at RETURN NEXT $ SELECT * FROM testit(); ERROR: wrong record TYPE supplied IN RETURN NEXT CONTEXT: PL/pgSQL FUNCTION "testit" line 7 at RETURN NEXT $ CREATE OR REPLACE FUNCTION testit() RETURNS setof test AS $$ DECLARE reply test; BEGIN reply.new_x := 123; reply.z := 456; RETURN NEXT reply; RETURN; END; $$ LANGUAGE plpgsql; CREATE FUNCTION $ SELECT * FROM testit(); new_x | z | new_column | new_column2 -------+-----+------------+------------- 123 | 456 | [NULL] | [NULL] (1 ROW)
Sweet. I think that if it altering datatype would actually invalidate all previously compiled plans/functions – it would be even better, but as I wrote earlier – when you're changing datatype you're likely to be changing function body as well, so it's not a big deal.
It might be also worth mentioning that if any table is using it as a column type, the type cannot be modified.
Note that you can also use
. I used this a bunch in PGXN::Manager. See for example
. I like how this keeps the implicit custom type associated with the function without requiring
parameters.
—Theory