When I create a new schema in Postgres through executing a script in psql
I want to put it in a tablespace. If this tablespace does not exist then I want to create it first. Since the default SQL does not have this option I created a function:
CREATE OR REPLACE FUNCTION make_tablespace(tablespace CHARACTER,
directory CHARACTER,
owner CHARACTER)
RETURNS void
AS
$$
BEGIN
IF tablespace = '' THEN
RAISE EXCEPTION 'No tablespace.';
END IF;
PERFORM SPCNAME FROM PG_TABLESPACE WHERE SPCNAME=tablespace;
IF NOT FOUND THEN
IF directory = '' THEN
RAISE EXCEPTION 'No directory.';
END IF;
IF owner = '' THEN
RAISE EXCEPTION 'No owner.';
END IF;
EXECUTE 'CREATE TABLESPACE '||tablespace||' OWNER '||owner||' LOCATION '''||directory||''';';
RAISE NOTICE 'Tablespace % created.', tablespace;
ELSE
RAISE NOTICE 'Tablespace % already exists.', tablespace;
END IF;
END $$ LANGUAGE plpgsql;
Unfortunately when I execute it (select make_tablespace('marco', '/opt/marco', 'marco');
) this gives an error:
ERROR: CREATE TABLESPACE cannot be executed from a function or multi-command string
I searched the internet and there seems to be a workaround (some years ago) by using the dblink
package. I do not want to install this. Is there today another way? I can return the SQL statement as a string but how to execute it then?
Best Answer
The point here is that Postgres functions are almost but not quite like true stored procedures. Postgres functions (unlike stored procedures) run in the context of an outer transaction. Therefore, you cannot execute commands that cannot run in a transaction block, like
VACUUM
,CREATE DATABASE
, ... orCREATE TABLESPACE
. The manual is clear about that:You have to run these commands as singular SQL commands. Postgres currently (up to version 9.6) also doesn't have autonomous transactions, which could be a workaround.
So the only workaround to include the command in a function or transaction is faking an autonomous transaction with
dblink
, just like you found already.There are some other minor problems in your code. I suggest:
Major points:
character
. You wanttext.
''
, also check forNULL
.format()
for short, clean dynamic SQL code and properly escape user input to avoid sneaky syntax errors and SQL injection.Related answer with code examples and more explanation:
Persistent inserts in a UDF even if the function aborts
Does Postgres support nested or autonomous transactions?