PostgreSQL: how do I set the search_path from inside a function? - sql

I need to set the search_path before a query. The new search path should be based on a function parameter. How exactly can I Do it? Right now I have:
CREATE FUNCTION get_sections(integer) RETURNS
table(id integer, name varchar, type varchar) as $$
SET search_path to $1, public;
select id, name, type from sections;
$$ language 'sql';
But it simply won't accept $1. I also tried with quote_ident($1) but it did not work.
Thanks!

Generic'ish solution
I created a pure sql function by using set_config().
This solution supports setting multiple schemas in a comma separated string. By default
the change applies to the current session. Setting the "is_local" parameter to true makes the change only apply to the current transaction, see http://www.postgresql.org/docs/9.4/static/functions-admin.html for more details.
CREATE OR REPLACE FUNCTION public.set_search_path(path TEXT, is_local BOOLEAN DEFAULT false) RETURNS TEXT AS $$
SELECT set_config('search_path', regexp_replace(path, '[^\w ,]', '', 'g'), is_local);
$$ LANGUAGE sql;
Since we're not running any dynamic sql there should be less chance of sql injection. Just to be sure I added some naive sanitizing of the text by removing all characters except alphanumerics,space and comma. Escaping/quoting the string was not trivial, but I'm not an expert, so.. =)
Remember that there is no feedback if you set a malformed path.
Here is some sample code for testing:
DROP SCHEMA IF EXISTS testschema CASCADE;
CREATE SCHEMA testschema;
CREATE TABLE testschema.mytable ( id INTEGER );
SELECT set_search_path('testschema, public');
SHOW search_path;
INSERT INTO mytable VALUES(123);
SELECT * FROM mytable;
A test based on OP's original code
Since we don't know the schema for mytable in advance, we need to use dynamic sql. I embedded the set_config-oneliner into the get_sections()-function instead of using the generic'ish function.
Note: I had to set is_local=false in set_config() for this to work. That means the modified path remains after the function is run. I'm not sure why.
DROP SCHEMA IF EXISTS testschema CASCADE;
CREATE SCHEMA testschema;
SET search_path TO public;
CREATE TABLE testschema.mytable ( id INTEGER, name varchar, type varchar );
INSERT INTO testschema.mytable VALUES (123,'name', 'some-type');
INSERT INTO testschema.mytable VALUES (567,'name2', 'beer');
CREATE OR REPLACE FUNCTION get_sections(schema_name TEXT) RETURNS
TABLE(id integer, name varchar, type varchar) AS $$
BEGIN
PERFORM set_config('search_path', regexp_replace(schema_name||', public', '[^\w ,]', '', 'g'), true);
EXECUTE 'SELECT id, name, type FROM mytable';
END;
$$ LANGUAGE plpgsql;
SET search_path TO public;
SELECT * FROM get_sections('testschema');
SHOW search_path; -- Unfortunately this has modified the search_path for the whole session.

Below code will work
CREATE FUNCTION get_sections(num integer) RETURNS
table(id integer, name varchar, type varchar) as $$
EXECUTE FORMAT('SET search_path to %L::INTEGER, public;'num);
select id, name, type from sections;
$$ language 'sql';

Related

Passing a ResultSet into a Postgresql Function

Is it possible to pass the results of a postgres query as an input into another function?
As a very contrived example, say I have one query like
SELECT id, name
FROM users
LIMIT 50
and I want to create a function my_function that takes the resultset of the first query and returns the minimum id. Is this possible in pl/pgsql?
SELECT my_function(SELECT id, name FROM Users LIMIT 50); --returns 50
You could use a cursor, but that very impractical for computing a minimum.
I would use a temporary table for that purpose, and pass the table name for use in dynamic SQL:
CREATE OR REPLACE FUNCTION f_min_id(_tbl regclass, OUT min_id int) AS
$func$
BEGIN
EXECUTE 'SELECT min(id) FROM ' || _tbl
INTO min_id;
END
$func$ LANGUAGE plpgsql;
Call:
CREATE TEMP TABLE foo ON COMMIT DROP AS
SELECT id, name
FROM users
LIMIT 50;
SELECT f_min_id('foo');
Major points
The first parameter is of type regclass to prevent SQL injection. More info in this related answer on dba.SE.
I made the temp table ON COMMIT DROP to limit its lifetime to the current transaction. May or may not be what you want.
You can extend this example to take more parameters. Search for code examples for dynamic SQL with EXECUTE.
-> SQLfiddle demo
I would take the problem on the other side, calling an aggregate function for each record of the result set. It's not as flexible but can gives you an hint to work on.
As an exemple to follow your sample problem:
CREATE OR REPLACE FUNCTION myMin ( int,int ) RETURNS int AS $$
SELECT CASE WHEN $1 < $2 THEN $1 ELSE $2 END;
$$ LANGUAGE SQL STRICT IMMUTABLE;
CREATE AGGREGATE my_function ( int ) (
SFUNC = myMin, STYPE = int, INITCOND = 2147483647 --maxint
);
SELECT my_function(id) from (SELECT * FROM Users LIMIT 50) x;
It is not possible to pass an array of generic type RECORD to a plpgsql function which is essentially what you are trying to do.
What you can do is pass in an array of a specific user defined TYPE or of a particular table row type. In the example below you could also swap out the argument data type for the table name users[] (though this would obviously mean getting all data in the users table row).
CREATE TYPE trivial {
"ID" integer,
"NAME" text
}
CREATE OR REPLACE FUNCTION trivial_func(data trivial[])
RETURNS integer AS
$BODY$
DECLARE
BEGIN
--Implementation here using data
return 1;
END$BODY$
LANGUAGE 'plpgsql' VOLATILE;
I think there's no way to pass recordset or table into function (but I'd be glad if i'm wrong). Best I could suggest is to pass array:
create or replace function my_function(data int[])
returns int
as
$$
select min(x) from unnest(data) as x
$$
language SQL;
sql fiddle demo

plpgsql - column type auto detect by returning a resultset

I got Postgresql 8.4
I have a user table
user_id INT,
user_name VARCHAR(255),
user_email VARCHAR(255),
user_salt VARCHAR(255)
and 2 functions:
one with SETOF:
CREATE FUNCTION test ()
RETURNS SETOF "user"
AS
$BODY$
BEGIN
RETURN QUERY SELECT
*
FROM
"user";
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
one with TABLE:
CREATE FUNCTION test ()
RETURNS TABLE (id INT, name VARCHAR, email VARCHAR)
AS
$BODY$
BEGIN
RETURN QUERY SELECT
"user".user_id, "user".user_name, "user".user_email
FROM
"user";
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
By SETOF the column type is filled automatically, but I cannot set the column name and which columns to select in the result. By TABLE I can cut off the user_ prefix and set the exact column names, but I have to set the column types manually.
Is it possible to got the advantages of both?
Type handling with in PostgreSQL (and SQL in general) is strict. Defining the RETURN type of a function can be tricky.
There are simple solutions with plain SQL:
Select and rename all columns:
SELECT * FROM t AS t(id, name, email, salt);
Select and rename some columns:
SELECT user_id AS id, user_name AS name FROM t;
Combine with function
If you need a server side function for some reason, you can still combine these SQL features with a function. Given a table t like defined in your question and this simple function:
CREATE OR REPLACE FUNCTION f_test()
RETURNS SETOF t AS
$func$
SELECT * FROM t -- do stuff here
$func$ LANGUAGE sql STABLE;
To only rename columns (no need to provide types):
SELECT * FROM f_test() t(id, name, email, salt)
Select and rename some columns:
SELECT user_id AS id, user_name AS name FROM f_test() t;
You could possibly combine this with various different functions on different tables:
Refactor a PL/pgSQL function to return the output of various SELECT queries
But I am not entirely sure which problem in particular you want to tackle here.
Asides
Never quote the language name. plpgsql is an identifier. Quoting can lead to unexpected problems.
A function that only selects from tables can be declared STABLE. May be beneficial with repeated calls when nested.
Generally I would advice to upgrade to a current version of Postgres. 8.4 is rather old and will be lose support in less than a year. There have been a number of improvements in this area since 8.4.
I don't think this is possible in pl/pgsql because, it strongly depends on user defined types. Sadly this language is not smart enough for type auto detection... I think my first possible solution I'll use, it solves the problem partially because at least I won't need to refactor every function manually by type change of a table column.
1.) Possible solution with asking column types:
CREATE FUNCTION test ()
RETURNS TABLE (id "user".user_id%TYPE, name "user".user_name%TYPE, email "user".user_email%TYPE)
AS
$BODY$
BEGIN
return QUERY SELECT
"user".user_id, "user".user_name, "user".user_email
FROM
"user";
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
With this at least the type is not redundant. Not the best, but acceptable.
2.) Possible solution with SETOF RECORD:
CREATE FUNCTION test ()
RETURNS SETOF RECORD
AS
$BODY$
BEGIN
RETURN QUERY SELECT
"user".user_id AS id, "user".user_name AS name, "user".user_email AS email
FROM
"user";
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
Without column type definition list I got the following error:
ERROR: a column definition list is required for functions returning
"record"
So I have to use it like this:
SELECT * FROM test() AS (id INT, name VARCHAR, email VARCHAR);
Instead of this:
SELECT * FROM test()
I got every column in string by the php client, so the column type definition is more than useless for me... This solution would be the best without column type definition, but with it not acceptable.
It is possible to use this similar to table:
CREATE FUNCTION test (OUT id "user".user_id%TYPE, OUT name "user".user_name%TYPE, OUT email "user".user_email%TYPE)
RETURNS SETOF RECORD
AS
$BODY$
BEGIN
RETURN QUERY SELECT
"user".user_id, "user".user_name, "user".user_email
FROM
"user";
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
For example I could set everything to TEXT:
CREATE FUNCTION test (OUT id TEXT, OUT name TEXT , OUT email TEXT )
RETURNS SETOF RECORD
AS
$BODY$
BEGIN
RETURN QUERY SELECT
"user".user_id::TEXT , "user".user_name::TEXT , "user".user_email::TEXT
FROM
"user";
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
This works, but this is far from type auto detection, and it would result a lot of redundant text converter code...
3.) Possible solution with refcursors:
CREATE FUNCTION test ()
RETURNS SETOF "user"
AS
$BODY$
DECLARE
refc "user";
BEGIN
FOR refc IN
SELECT
"user".user_id, "user".user_name, "user".user_email
FROM
"user"
LOOP
RETURN NEXT refc;
END LOOP ;
RETURN ;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
This fills out the lacking columns with null values, I cannot name the columns, and sql loops are very slow... So this is not acceptable.
By refcursors there is another way: to return the refcursor itself, but it is not acceptable because I cannot use it as a normal variable, I have to give a string as cursor name... Btw I did not manage to use the refcursor itself as result in phpstorm. I got jdbc cursor not found error. Maybe I set the name wrong, I don't know, I don't think it's worth more effort.

How to create SQL function with parameter?

I have an SQL function:
-- Function: insert_into_wgs()
-- DROP FUNCTION insert_into_wgs();
CREATE OR REPLACE FUNCTION insert_into_wgs()
RETURNS void AS
$BODY$
BEGIN
INSERT INTO parcels (num,vid,support_num,name_dispatcher,cadastr,the_geom,status_id)
SELECT num,vid,support_num,name_dispatcher,cadastr,ST_TRANSFORM(the_geom,4326),status_id
FROM parcels_temp
WHERE num NOT IN (SELECT num FROM parcels)AND deleted='no';
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION insert_into_wgs() OWNER TO postgres;
You see that it works only for parcels, parcels_temp tables. How can I create function with parameters insert_into_wgs(table, table_temp);?
As mentioned in the comment, you have to use dynamic SQL if you want to parametrize identifiers. And use EXECUTE in plpgsql.
In the base query better use NOT EXISTS instead of col NOT IN (<subquery>).
Now, if only the table names change and the columns stay the same, this simple demo would do the job:
CREATE OR REPLACE FUNCTION insert_into_tbl(_tbl text, _tbl_tmp text)
RETURNS void AS
$func$
BEGIN
EXECUTE format($x$
INSERT INTO %I (...) -- enter columns
SELECT ... -- enter columns
FROM %I t
WHERE NOT EXISTS (
SELECT 1
FROM %1$I p -- reuse first table name
WHERE p.num = t.num
)
AND deleted = 'no'$x$, _tbl, _tbl_tmp);
END
$func$ LANGUAGE plpgsql;
Check out format() in the manual.
There are quite a few related answers on SO. Try this search.

use selected value as table name in postgres

i've serarched for the answer, but did't find.
so i've got a table types
CREATE TABLE types
(
type_id serial NOT NULL,
type_name character varying,
CONSTRAINT un_type_name UNIQUE (type_name)
)
which holds type names, lets say users - and this is the name of corresponding table users. this design may be a bit ugly, but it was made to allow users create their own types. (is there better way to acheve this?)
now i want to perform a query like this one:
select type_name, (select count(*) from ???) from types
to get list of all type names and count of objects of each type.
can this be done?
You cannot do it directly in SQL
You can use a PLpgSQL function and dynamic SQL
CREATE OR REPLACE FUNCTION tables_count(OUT type_name character varying, OUT rows bigint)
RETURNS SETOF record AS $$
BEGIN
FOR tables_count.type_name IN SELECT types.type_name FROM types
LOOP
EXECUTE 'SELECT COUNT(*) FROM ' || quote_ident(tables_count.type_name) INTO tables_count.rows;
RETURN NEXT;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM tables_count();
I don't have enough information, but I do suspect that something is off with your design. You shouldn't need an extra table for every type.
Be that as it may, what you want to do cannot be done - in pure SQL.
It can be done with a plpgsql function executing dynamic SQL, though:
CREATE OR REPLACE FUNCTION f_type_ct()
RETURNS TABLE (type_name text, ct bigint) AS
$BODY$
DECLARE
tbl text;
BEGIN
FOR tbl IN SELECT t.type_name FROM types t ORDER BY t.type_name
LOOP
RETURN QUERY EXECUTE
'SELECT $1, count(*) FROM ' || tbl::regclass
USING tbl;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql;
Call:
SELECT * FROM f_type_ct();
You'll need to study most of the chapter about plpgsql in the manual to understand what's going on here.
One special hint: the cast to regclass is a safeguard against SQLi. You could also use the more generally applicable quote_ident() for that, but that does not properly handle schema-qualified table names, while the cast to regclass does. It also only accepts table names that are visible to the calling user.

PostgreSQL create table if not exists

In a MySQL script you can write:
CREATE TABLE IF NOT EXISTS foo ...;
... other stuff ...
and then you can run the script many times without re-creating the table.
How do you do this in PostgreSQL?
This feature has been implemented in Postgres 9.1:
CREATE TABLE IF NOT EXISTS myschema.mytable (i integer);
For older versions, here is a function to work around it:
CREATE OR REPLACE FUNCTION create_mytable()
RETURNS void
LANGUAGE plpgsql AS
$func$
BEGIN
IF EXISTS (SELECT FROM pg_catalog.pg_tables
WHERE schemaname = 'myschema'
AND tablename = 'mytable') THEN
RAISE NOTICE 'Table myschema.mytable already exists.';
ELSE
CREATE TABLE myschema.mytable (i integer);
END IF;
END
$func$;
Call:
SELECT create_mytable(); -- call as many times as you want.
Notes
The columns schemaname and tablename in pg_tables are case-sensitive. If you double-quote identifiers in the CREATE TABLE statement, you need to use the exact same spelling. If you don't, you need to use lower-case strings. See:
Are PostgreSQL column names case-sensitive?
pg_tables only contains actual tables. The identifier may still be occupied by related objects. See:
How to check if a table exists in a given schema
If the role executing this function does not have the necessary privileges to create the table you might want to use SECURITY DEFINER for the function and make it owned by another role with the necessary privileges. This version is safe enough.
Try this:
CREATE TABLE IF NOT EXISTS app_user (
username varchar(45) NOT NULL,
password varchar(450) NOT NULL,
enabled integer NOT NULL DEFAULT '1',
PRIMARY KEY (username)
)
I created a generic solution out of the existing answers which can be reused for any table:
CREATE OR REPLACE FUNCTION create_if_not_exists (table_name text, create_stmt text)
RETURNS text AS
$_$
BEGIN
IF EXISTS (
SELECT *
FROM pg_catalog.pg_tables
WHERE tablename = table_name
) THEN
RETURN 'TABLE ' || '''' || table_name || '''' || ' ALREADY EXISTS';
ELSE
EXECUTE create_stmt;
RETURN 'CREATED';
END IF;
END;
$_$ LANGUAGE plpgsql;
Usage:
select create_if_not_exists('my_table', 'CREATE TABLE my_table (id integer NOT NULL);');
It could be simplified further to take just one parameter if one would extract the table name out of the query parameter. Also I left out the schemas.
This solution is somewhat similar to the answer by Erwin Brandstetter, but uses only the sql language.
Not all PostgreSQL installations has the plpqsql language by default, this means you may have to call CREATE LANGUAGE plpgsql before creating the function, and afterwards have to remove the language again, to leave the database in the same state as it was before (but only if the database did not have the plpgsql language to begin with). See how the complexity grows?
Adding the plpgsql may not be issue if you are running your script locally, however, if the script is used to set up schema at a customer it may not be desirable to leave changes like this in the customers database.
This solution is inspired by a post by Andreas Scherbaum.
-- Function which creates table
CREATE OR REPLACE FUNCTION create_table () RETURNS TEXT AS $$
CREATE TABLE table_name (
i int
);
SELECT 'extended_recycle_bin created'::TEXT;
$$
LANGUAGE 'sql';
-- Test if table exists, and if not create it
SELECT CASE WHEN (SELECT true::BOOLEAN
FROM pg_catalog.pg_tables
WHERE schemaname = 'public'
AND tablename = 'table_name'
) THEN (SELECT 'success'::TEXT)
ELSE (SELECT create_table())
END;
-- Drop function
DROP FUNCTION create_table();
There is no CREATE TABLE IF NOT EXISTS... but you can write a simple procedure for that, something like:
CREATE OR REPLACE FUNCTION execute(TEXT) RETURNS VOID AS $$
BEGIN
EXECUTE $1;
END; $$ LANGUAGE plpgsql;
SELECT
execute($$
CREATE TABLE sch.foo
(
i integer
)
$$)
WHERE
NOT exists
(
SELECT *
FROM information_schema.tables
WHERE table_name = 'foo'
AND table_schema = 'sch'
);
There is no CREATE TABLE IF NOT EXISTS... but you can write a simple procedure for that, something like:
CREATE OR REPLACE FUNCTION prc_create_sch_foo_table() RETURNS VOID AS $$
BEGIN
EXECUTE 'CREATE TABLE /* IF NOT EXISTS add for PostgreSQL 9.1+ */ sch.foo (
id serial NOT NULL,
demo_column varchar NOT NULL,
demo_column2 varchar NOT NULL,
CONSTRAINT pk_sch_foo PRIMARY KEY (id));
CREATE INDEX /* IF NOT EXISTS add for PostgreSQL 9.5+ */ idx_sch_foo_demo_column ON sch.foo(demo_column);
CREATE INDEX /* IF NOT EXISTS add for PostgreSQL 9.5+ */ idx_sch_foo_demo_column2 ON sch.foo(demo_column2);'
WHERE NOT EXISTS(SELECT * FROM information_schema.tables
WHERE table_schema = 'sch'
AND table_name = 'foo');
EXCEPTION WHEN null_value_not_allowed THEN
WHEN duplicate_table THEN
WHEN others THEN RAISE EXCEPTION '% %', SQLSTATE, SQLERRM;
END; $$ LANGUAGE plpgsql;