How do I reference tables in the same schema as my function? - postgresql

I am creating a function within a schema that I am going to make a series of queries from. I want all of these queries to be made as if the schema of the function was first in the search path. I know I can change the search path within the context of the function, but I can't find how to learn the schema that my function is contained in from within my function.

You can try to parse the pg_context return value of get diagnostics:
create schema test_schema;
create or replace function test_schema.test_function()
returns text language plpgsql as $$
declare
stack text;
begin
get diagnostics stack = pg_context;
return stack;
end $$;
select test_schema.test_function();
test_function
-------------------------------------------------------------------------
PL/pgSQL function test_schema.test_function() line 5 at GET DIAGNOSTICS
(1 row)
If the function name is unique you can get the schema name querying the system catalog pg_namespace:
create or replace function test_schema.test_function_2()
returns text language sql as $$
select nspname::text
from pg_namespace n
join pg_proc p on n.oid = pronamespace
where proname = 'test_function_2'
$$;
select test_schema.test_function_2() as schema_name;
schema_name
-------------
test_schema
(1 row)

Related

How to know the parent schema name from inside a function

From inside a function, I need to know in which schema the function belongs to. Is there any way to know that in Postgresql?
FYI, current_schema() function provides me the set schema of the current session, which is not what I want. For example, I am in schema test1 now and I can call function test2.test_function(). Now, from inside that function current_schema() will give test1. But I need to get test2, which is the function schema.
After doing some research and connecting several SO answers, I come up with this solution.
CREATE OR REPLACE FUNCTION parent_schema()
RETURNS text AS $$
DECLARE
stack text; fcesig text;
function_oid oid;
schema_name text;
BEGIN
GET DIAGNOSTICS stack = PG_CONTEXT;
fcesig := substring(stack from 'function (.*?) line');
function_oid := fcesig::regprocedure::oid;
SELECT routine_schema INTO schema_name FROM information_schema.routines WHERE regexp_replace(specific_name, '^.+?([^_]+)$', '\1')::int = function_oid;
RETURN schema_name;
END;
$$ LANGUAGE plpgsql;
The answers which helped me to form this :
https://stackoverflow.com/a/32016935/5645769
https://stackoverflow.com/a/1347639/5645769
https://stackoverflow.com/a/24034604/5645769
As far as I can tell this is not possible in a reliable way.
The function you provide in your answer does not work:
CREATE FUNCTION public.myfunc() RETURNS text
LANGUAGE sql
AS 'SELECT laurenz.parent_schema()';
CREATE FUNCTION laurenz.myfunc() RETURNS text
LANGUAGE sql
AS 'SELECT laurenz.parent_schema()';
Your function gives the wrong answer:
test=> SELECT public.myfunc();
myfunc
---------
laurenz
(1 row)

Postgresql9-4 - User define function that uses a join - ERROR: missing FROM-clause entry for table

I am venturing into pg/sql for the first time and I wrote this function that (attempts) to return results from a query that uses a JOIN.
The query works when I run on it self. However, when it is running within the function I wrote it complains about a missing "FROM". I also used table aliases hoping that it may fix the problem but it did not.
I am getting information from the following tables and rows.
TABLE | ROW
---------------------------------------
banned_users | banned_lcl_account
---------------------------------------
rhost_active_users | active_users
Here is the error when I run my function
ERROR: missing FROM-clause entry for table "a_u"
LINE 1: SELECT LOOP_V.a_u.ipaddress
And here is the function I wrote.
CREATE OR REPLACE FUNCTION GET_BANNED_ACTIVE_USERS()
RETURNS TABLE
(
SRC_HOST TEXT,
DST_HOST TEXT
)
AS $$
DECLARE
LOOP_V RECORD;
BEGIN
FOR LOOP_V IN (
select
b_u.banned_lcl_account,
a_u.ipaddress,
a_u.hostip,
date_trunc ('second', a_u.time_captured)
from banned_users as b_u
inner join rhost_active_users as a_u
on b_u.banned_lcl_account = a_u.active_users
and a_u.hostip <> 'TTY Login'
or a_u.hostip <> 'Local PTS Login'
)
LOOP
SRC_HOST := LOOP_V.a_u.ipaddress;
DST_HOST := LOOP_V.a_u.hostip;
END LOOP;
END; $$
LANGUAGE 'plpgsql';
--select CHECK_BANNED_ACCOUNTS()
your problem is this:
SRC_HOST := LOOP_V.a_u.ipaddress;
DST_HOST := LOOP_V.a_u.hostip;
The record that is used for the loop variable only contains column names, not their table aliases. So the record only has the fields: ipaddress and hostip. So your immediate problem can be fixed using:
SRC_HOST := LOOP_V.ipaddress;
DST_HOST := LOOP_V.hostip;
However the whole function is overly complex. You don't need the loop nor do you need PL/pgSQL. This can be written as a simple SQL function:
CREATE OR REPLACE FUNCTION GET_BANNED_ACTIVE_USERS()
RETURNS TABLE (SRC_HOST TEXT,DST_HOST TEXT)
AS $$
select a_u.ipaddress,
a_u.hostip,
from banned_users as b_u
join rhost_active_users as a_u
on b_u.banned_lcl_account = a_u.active_users
and (a_u.hostip <> 'TTY Login' or a_u.hostip <> 'Local PTS Login')
$$
LANGUAGE sql;
Note that I also removed the unused columns from the SELECT statement in order to match the function's signature of returning two columns
Some additional notes:
--select CHECK_BANNED_ACCOUNTS()
This is wrong, you need to use a set returning function like a table:
select *
from CHECK_BANNED_ACCOUNTS();
And finally:
LANGUAGE 'plpgsql';
The language name is an identifier, do not enclose it in single quotes:
LANGUAGE plpgsql;
or
LANGUAGE sql;

PL/pgSQL CREATE or REPLACE within EXECUTE

I have the following script to dynamically create views into a PostgreSQL database.
CREATE OR REPLACE FUNCTION cs_refresh_mviews() RETURNS integer AS $$
DECLARE
mviews RECORD;
query text;
park_name text;
ppstatements int;
BEGIN
RAISE NOTICE 'Creating views...';
FOR mviews IN SELECT name FROM "Canadian_Parks" LOOP
park_name := mviews.name;
RAISE NOTICE 'Creating or replace view %s...', mviews.name;
query := 'CREATE OR REPLACE VIEW %_view AS
SELECT * from "Canadian_Parks" where name=''%'';
ALTER TABLE %_view OWNER TO postgres', park_name, park_name, park_name;
-- RAISE NOTICE query;
EXECUTE query;
END LOOP;
RAISE NOTICE 'Done refreshing materialized views.';
RETURN 1;
END;
$$ LANGUAGE plpgsql;
I have confirmed integrity of the string, such as
CREATE OR REPLACE VIEW Saguenay_St__Lawrence_view AS
SELECT * from "Canadian_Parks" where name='Saguenay_St__Lawrence';
ALTER TABLE Saguenay_St__Lawrence_view OWNER TO postgres
assigned to the query variable by manually submitting this to the database and getting a successful response.
However, if I attempt to execute the function using
SELECT cs_refresh_mviews();
the followig error is displayed:
ERROR: query "SELECT 'CREATE OR REPLACE VIEW %_view AS SELECT * from "Canadian_Parks" where name=''%''; ALTER TABLE %_view OWNER TO postgres', park_name, park_name, park_name" returned 4 columns
CONTEXT: PL/pgSQL function "cs_refresh_mviews" line 32 at assignment
********** Error **********
ERROR: query "SELECT 'CREATE OR REPLACE VIEW %_view AS SELECT * from "Canadian_Parks" where name=''%''; ALTER TABLE %_view OWNER TO postgres', park_name, park_name, park_name" returned 4 columns
SQL state: 42601
Context: PL/pgSQL function "cs_refresh_mviews" line 32 at assignment
Why has this been converted to a SELECT statement, instead of a pure CREATE?
You setup is pretty twisted. Why would you save part of the name of a view in a composite type of a table instead of saving it in a plain text column?
Anyhow, it could work like this:
Setup matching question:
CREATE SCHEMA x; -- demo in test schema
SET search_path = x;
CREATE TYPE mviews AS (id int, name text); -- composite type used in table
CREATE TABLE "Canadian_Parks" (name mviews);
INSERT INTO "Canadian_Parks"(name) VALUES
('(1,"canadian")')
,('(2,"islandic")'); -- composite types, seriously?
SELECT name, (name).* from "Canadian_Parks";
CREATE OR REPLACE FUNCTION cs_refresh_mviews()
RETURNS int LANGUAGE plpgsql SET search_path = x AS -- search_path for test
$func$
DECLARE
_parkname text;
BEGIN
FOR _parkname IN SELECT (name).name FROM "Canadian_Parks" LOOP
EXECUTE format('
CREATE OR REPLACE VIEW %1$I AS
SELECT * FROM "Canadian_Parks" WHERE (name).name = %2$L;
ALTER TABLE %1$I OWNER TO postgres'
, _parkname || '_view', _parkname);
END LOOP;
RETURN 1;
END
$func$;
SELECT cs_refresh_mviews();
DROP SCHEMA x CASCADE; -- clean up
Major points
As you are executing text with execute, you need to safeguard against SQL injection. I use the format() function for identifiers and the literal
I use the syntax SELECT (name).name to cope with your weird setup and extract the name we need right away.
Similarly, the VIEW needs to read WHERE (name).name = .. to work in this setup.
I removed a lot of noise that is irrelevant to the question.
It's also probably pointless to have the function RETURN 1. Just define the function with RETURNS void. I kept it, though, to match the question.
Untangled setup
How it probably should be:
CREATE SCHEMA x;
SET search_path = x;
CREATE TABLE canadian_parks (id serial primary key, name text);
INSERT INTO canadian_parks(name) VALUES ('canadian'), ('islandic');
SELECT * from canadian_parks;
CREATE OR REPLACE FUNCTION cs_refresh_mviews()
RETURNS void LANGUAGE plpgsql SET search_path = x AS
$func$
DECLARE
parkname text;
BEGIN
FOR parkname IN SELECT name FROM canadian_parks LOOP
EXECUTE format('
CREATE OR REPLACE VIEW %1$I AS
SELECT * FROM canadian_parks WHERE name = %2$L;
ALTER TABLE %1$I OWNER TO postgres'
, parkname || '_view', parkname);
END LOOP;
END
$func$;
SELECT cs_refresh_mviews();
DROP SCHEMA x CASCADE;
You've misunderstood usage of commas in assignment expression.
It turns query to array (RECORD) instead of scalar.
Use concatenation:
park_name := quote_ident(mviews.name||'_view');
query := 'CREATE OR REPLACE VIEW '||park_name||' AS SELECT * from "Canadian_Parks" where name='||quote_literal(mviews.name)||'; ALTER TABLE '||park_name||' OWNER TO postgres';

How to get function parameter lists (so I can drop a function)

I want to get the SQL to drop a function in PostgreSQL. I write DROP FUNCTION and a get function name from pg_proc. That is not problem. However if I leave blank parameters it will not drop the function.
I checked the manual and there is written then I have to identify the function with its parameters to drop it, eg DROP FUNCTION some_func(text,integer) not just DROP FUNCTION some_func.
Where can I find the parameters? In the function's row on in the pg_proc table there is no parameters. So how can I get the SQL to drop the function?
Postgres has a dedicated function for that purpose. Introduced with Postgres 8.4. The manual:
pg_get_function_identity_arguments(func_oid) ... get argument list to identify a function (without default values) ...
pg_get_function_identity_arguments returns the argument list
necessary to identify a function, in the form it would need to appear
in within ALTER FUNCTION, for instance. This form omits default values.
Using that (and format(), introduced with Postgres 9.1), the following query generates DDL statements to drop functions matching your search terms:
SELECT format('DROP %s %I.%I(%s);'
, CASE WHEN p.proisagg THEN 'AGGREGATE' ELSE 'FUNCTION' END
, n.nspname
, p.proname
, pg_catalog.pg_get_function_identity_arguments(p.oid)
) AS stmt
FROM pg_catalog.pg_proc p
JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
WHERE p.proname = 'dblink' -- function name
-- AND n.nspname = 'public' -- schema name (optional)
-- AND pg_catalog.pg_function_is_visible(p.oid) -- function visible to user
ORDER BY 1;
The system catalog pg_proc changed in Postgres 11. proisagg was replaced by prokind, true stored procedures were added. You need to adapt. See:
How to drop all of my functions in PostgreSQL?
Returns:
stmt
---------------------------------------------------
DROP FUNCTION public.dblink(text);
DROP FUNCTION public.dblink(text, boolean);
DROP FUNCTION public.dblink(text, text);
DROP FUNCTION public.dblink(text, text, boolean);
Found four matches in the example because dblink uses overloaded functions.
Run DROP statements selectively!
Alternatively, you can use the convenient cast to the object identifier type regprocedure which returns a complete function signature including argument types:
-- SET LOCAL search_path = ''; -- optional, to get all names schema-qualified
SELECT format('DROP %s %s;'
, CASE WHEN proisagg THEN 'AGGREGATE' ELSE 'FUNCTION' END
, oid::regprocedure
) AS stmt
FROM pg_catalog.pg_proc
WHERE proname = 'dblink' -- function name
ORDER BY 1;
In Postgres 10, you can delete a function without knowing the list of parameters, as long as it is unique in its schema.
drop function if exists some_func;
See the docs.
Of course, if you have overloaded the function (or are trying to delete over multiple schemas), you will still need the above answers.
use pgadminIII and direct access to function list and right click it then select delete
If you are working on an old previous version of postgres, for which pg_get_function_identity_arguments(func_oid) doesn't exist, I create my own function get the parameters from the function, you only need to pass the oid for the function, you need to deploy the function below to your postgres db.
CREATE OR REPLACE FUNCTION public.getFunctionParameter(functionOid oid)
RETURNS text AS
$BODY$
declare
t_paras text;
paras oid[];
res text :='(';
begin
select proargtypes into t_paras from pg_proc where oid=functionOid;
if t_paras is null or t_paras='' then
return '()';
else
paras:=string_to_array(t_paras,' ');
for i in array_lower(paras,1) .. array_upper(paras,1)
loop
raise notice 'para is %',paras[i];
select format_type(paras[i]::oid,NULL) into t_paras;
res:=res||t_paras||',';
end loop;
res:=substring(res from 1 for char_length(res)-1);
res:=res||')';
return res;
end if;
end
$BODY$
LANGUAGE plpgsql ;
The function below will list the function name and parameters, change the schema name if you want to get function under some other schema, I am using public for example
SELECT n.nspname||'.'||p.proname||public.getFunctionParameter(p.oid)
FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname='public'
You will the result like below
1 "public.getfunctionparameter(integer,text)"
2 "public.getfunctionparameter(oid)"

I want to have my pl/pgsql script output to the screen

I have the following script that I want output to the screen from.
CREATE OR REPLACE FUNCTION randomnametest() RETURNS integer AS $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN SELECT * FROM my_table LOOP
SELECT levenshtein('mystring',lower('rec.Name')) ORDER BY levenshtein;
END LOOP;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
I want to get the output of the levenshein() function in a table along with the rec.Name. How would I do that? Also, it is giving me an error about the line where I call levenshtein(), saying that I should use perform instead.
Assuming that you want to insert the function's return value and the rec.name into a different table. Here is what you can do (create the table new_tab first)-
SELECT levenshtein('mystring',lower(rec.Name)) AS L_val;
INSERT INTO new_tab (L_val, rec.name);
The usage above is demonstrated below.
I guess, you can use RAISE INFO 'This is %', rec.name; to view the values.
CREATE OR REPLACE FUNCTION randomnametest() RETURNS integer AS $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN SELECT * FROM my_table LOOP
SELECT levenshtein('mystring',lower(rec.Name))
AS L_val;
RAISE INFO '% - %', L_val, rec.name;
END LOOP;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
Note- the FROM clause is optional in case you select from a function in a select like netxval(sequence_name) and don't have any actual table to select from i.e. like SELECT nextval(sequence_name) AS next_value;, in Oracle terms it would be SELECT sequence_name.nextval FROM dual; or SELECT function() FROM dual;. There is no dual in postgreSQL.
I also think that the ORDER BY is not necessary since my assumption would be that your function levenshtein() will most likely return only one value at any point of time, and hence wouldn't have enough data to ORDER.
If you want the output from a plpgsql function like the title says:
CREATE OR REPLACE FUNCTION randomnametest(_mystring text)
RETURNS TABLE (l_dist int, name text) AS
$BODY$
BEGIN
RETURN QUERY
SELECT levenshtein(_mystring, lower(t.name)), t.name
FROM my_table t
ORDER BY 1;
END;
$$ LANGUAGE plpgsql;
Declare the table with RETURNS TABLE.
Use RETURN QUERY to return records from the function.
Avoid naming conflicts between column names and OUT parameters (from the RETURNS TABLE clause) by table-qualifying column names in queries. OUT parameters are visible everywhere in the function body.
I made the string to compare to a parameter to the function to make this more useful.
There are other ways, but this is the most effective for the task. You need PostgreSQL 8.4 or later.
For a one-time use I would consider to just use a plain query (= function body without the RETURN QUERY above).