How to fetch PL/pgSQL cursors? - plpgsql

I am trying to get dynamic data using cursors :
CREATE FUNCTION myfunc(refcursor, refcursor) RETURNS SETOF refcursor AS $$
BEGIN
OPEN $1 FOR SELECT * FROM users;
RETURN NEXT $1;
END;
$$ LANGUAGE plpgsql;
When i try to fetch data i keep getting this error : "a" is not a known variable ??
do $$ begin
SELECT * FROM myfunc('a', 'b');
FETCH ALL FROM a;
COMMIT;
end;
$$

If all you want to do is fetch the data from the cursor, you don't need PL/pgSQL code. All you need is a transaction, since cursors don't outlive a transaction. Don't mix up BEGIN from PL/pgSQL, which starts a code block, with BEGIN from SQL, which starts a transaction:
BEGIN;
SELECT * FROM myfunc('a', 'b');
FETCH ALL FROM a;
COMMIT;

Related

How to use the defined the cursor returned from other function

I have a simple function that returns a cursor to the function caller.
create or replace function func_get_cursor()
returns refcursor as $$
declare
my_cursor refcursor:='hello_cursor';
r record;
begin
open my_cursor for select empno,ename, job from emp;
return my_cursor;
end;
$$ language plpgsql;
Then I define another function that want to use the above defined cursor:
create or replace function func_use_cursor()
returns setof record as $$
declare
my_cursor refcursor;
begin
select func_get_cursor() into my_cursor;
fetch 4 from my_cursor;
commit;
-- how to collect the result rows and return
return;
end;
$$ language plpgsql;
There are 2 problems here:
it complains that there are errors for the ;around fetch 4 from my_cursor;,but i don't find out where the problem is.
I want to fetch 4 rows from the cursor, and return the 4 rows(the return type is setof record), I would ask how to collect the result rows and return.
Thanks!

Select Statement using Stored Procedure

May I ask on how to call a method when the content of the stored procedure is about select statement? (Using postgreSQL)
CREATE OR REPLACE PROCEDURE select_table(table_name VARCHAR(255))
language plpgsql
as $$
BEGIN
EXECUTE('SELECT * FROM' || ' ' || quote_ident(table_name));
END $$;
CALL select_table('employee_table');
EDITED(USING FUNCTION)
CREATE OR REPLACE FUNCTION select_table(table_name VARCHAR(255))
language plpgsql
as $$
BEGIN
SELECT * FROM table_name
RETURN table_name;
END $$;
In PostgreSQL procedures doesn't execute any select statements and doesn't have return.
For returning data you can use functions. But functions also cannot return different structural data, examples:
CREATE OR REPLACE FUNCTION fr_test()
RETURNS TABLE(id integer, bookname character varying)
LANGUAGE plpgsql
AS $function$
begin
return QUERY
SELECT tb.id, tb.bookname from rbac.books tb;
end;
$function$
;
or
CREATE OR REPLACE FUNCTION fr_test()
RETURNS setof public.books
LANGUAGE plpgsql
AS $function$
begin
return QUERY
SELECT * from public.books;
end;
$function$
;
But for returning difference tables you can do it using procedures and using out refcursor, like as in Oracle. For example:
create or replace procedure pr_test(OUT r1 refcursor)
as $$
begin
open r1 for
select * from public.books;
end;
$$ language plpgsql;

Fetch stored procedure refcursor output and insert into temp table

I have a stored procedure(P1) that returns a refcursor and few other text datatype values.
I have another procedure(P2) where I need to the fetch P1's refcursor output and insert it into a temporary table. The temporary table has matching columns and datatypes.
create or replace procedure P1(inout rfcur refcursor, in dtl text)
as
$$
begin
open rfcur for select * from tst_dump where ident = dtl;
end;
$$
language plpgsql;
create or replace P2(inout rfc refcursor, in dt_array text[])
as
$$
declare
i record;
cur refcursor;
begin
for i in array_lower(dt_array, 1)..array_upper(dt_array, 1) loop
call P1(cur, i);
--I need to fetch the result set from `cur` and store into a temp table `t_tab1`.
end loop;
end;
$$
language plpgsql;
Is it possible to achieve this in Postgres?
NOTE: I'm not supposed to make any changes to the procedure P1.
p2 could look like this:
CREATE PROCEDURE p2(IN dt_array text[])
LANGUAGE plpgsql AS
$$DECLARE
r record;
i integer;
cur refcursor;
BEGIN
FOR i IN array_lower(dt_array, 1)..array_upper(dt_array, 1) LOOP
CALL p1(cur, i::text);
LOOP
FETCH cur INTO r;
EXIT WHEN NOT FOUND;
INSERT INTO t_tab1 (...) VALUES (r.col1, r.col2, ...;
END LOOP;
END LOOP;
END;$$;
You should indent your code. That's the basic requirement when you program.
It seems to me that you are doing something the wrong way. Using procedures and cursors complicates everything and makes it slower.
You should do something like
INSERT INTO t_tab
SELECT /* your original query */;

How should I extract duplicated logic in a Postgres function?

I have a Postgres function with a lot of duplicated logic. If I were writing this in, say, Ruby, I would extract the duplicated logic into a few private helper methods. But there doesn't seem to be an equivalent of "private methods" in Postgres.
Original Function
CREATE OR REPLACE FUNCTION drop_create_idx_constraint(in_operation varchar, in_table_name_or_all_option varchar) RETURNS integer AS $$
DECLARE
cur_drop_for_specific_tab CURSOR (tab_name varchar) IS SELECT drop_stmt FROM table_indexes WHERE table_indexes.table_name = table_name_to_drop;
cur_drop_for_all_tab CURSOR IS SELECT drop_stmt FROM table_indexes;
cur_create_for_specific_tab CURSOR (tab_name varchar) IS SELECT recreate_stmt FROM table_indexes WHERE table_indexes.table_name = table_name_to_drop;
cur_create_for_all_tab CURSOR IS SELECT recreate_stmt FROM table_indexes;
BEGIN
IF upper(in_operation) = 'DROP' THEN
IF upper(in_table_name_or_all_option) ='ALL' THEN
FOR table_record IN cur_drop_for_all_tab LOOP
EXECUTE table_record.drop_stmt;
END LOOP;
ELSE
FOR table_record IN cur_drop_for_specific_tab(in_table_name_or_all_option) LOOP
EXECUTE table_record.drop_stmt;
END LOOP;
END IF;
ELSIF upper(in_operation) = 'CREATE' THEN
IF upper(in_table_name_or_all_option) ='ALL' THEN
FOR table_record IN cur_create_for_all_tab LOOP
EXECUTE table_record.recreate_stmt;
END LOOP;
ELSE
FOR table_record IN cur_create_for_specific_tab(in_table_name_or_all_option) LOOP
EXECUTE table_record.recreate_stmt;
END LOOP;
END IF;
END IF;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
Refactored Function(s)
CREATE OR REPLACE FUNCTION execute_recreate_stmt_from_records(input_cursor refcursor) RETURNS integer AS $$
BEGIN
FOR table_record IN input_cursor LOOP
EXECUTE table_record.recreate_stmt;
END LOOP;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION execute_drop_stmt_from_records(input_cursor refcursor) RETURNS integer AS $$
BEGIN
FOR table_record IN input_cursor LOOP
EXECUTE table_record.drop_stmt;
END LOOP;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION drop_indexes_and_constraints(table_name_to_drop varchar) RETURNS integer AS $$
DECLARE
indexes_and_constraints CURSOR IS SELECT drop_stmt FROM table_indexes WHERE table_indexes.table_name = table_name_to_drop;
SELECT execute_drop_stmt_from_records(indexes_and_constraints);
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION drop_all_indexes_and_constraints() RETURNS integer AS $$
DECLARE
indexes_and_constraints CURSOR IS SELECT drop_stmt FROM table_indexes;
SELECT execute_drop_stmt_from_records(indexes_and_constraints);
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION recreate_indexes_and_constraints(table_name_to_recreate varchar) RETURNS integer AS $$
DECLARE
indexes_and_constraints CURSOR IS SELECT recreate_stmt FROM table_indexes WHERE table_indexes.table_name = table_name_to_recreate;
SELECT execute_recreate_stmt_from_records(indexes_and_constraints);
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION recreate_all_indexes_and_constraints() RETURNS integer AS $$
DECLARE
indexes_and_constraints CURSOR IS SELECT recreate_stmt FROM table_indexes;
SELECT execute_recreate_stmt_from_records(indexes_and_constraints);
$$ LANGUAGE plpgsql;
I believe the underlying problem with my refactor is that the helper functions, execute_recreate_stmt_from_records and execute_drop_stmt_from_records, are way too powerful to be publicly accessible, especially since Heroku (which hosts this DB) only allows one DB user. Of course, if there are other problems with the above refactor, feel free to point them out.
You can reach separation by moving "private" procedures into a new schema, limiting access to it. Then use a SECURITY DEFINER to allow calls to "private" functions.
Although, this will be hard to achieve if you are limited to a single user by your hosting service.
Example:
CREATE USER app_user;
CREATE USER private_user;
GRANT ALL ON DATABASE my_database TO app_user;
GRANT CONNECT, CREATE ON DATABASE my_database TO private_user;
-- With private_user:
CREATE SCHEMA private;
CREATE OR REPLACE FUNCTION private.test_func1()
RETURNS integer AS
$BODY$
BEGIN
RETURN 123;
END
$BODY$
LANGUAGE plpgsql STABLE
COST 100;
CREATE OR REPLACE FUNCTION public.my_function_1()
RETURNS integer AS
$BODY$
DECLARE
BEGIN
RETURN private.test_func1();
END
$BODY$
LANGUAGE plpgsql VOLATILE SECURITY DEFINER
COST 100;
-- With app_user:
SELECT private.test_func1(); -- ERROR: permission denied for schema private
SELECT my_function_1(); -- Returns 123

Postgresql - Using multi result sets in another function

I follow this tutorial in order create a function
http://www.sqlines.com/postgresql/how-to/return_result_set_from_stored_procedure
Here is the code
CREATE FUNCTION func1(vtoken character varying, ref1 refcursor, ref2 refcursor)
RETURNS SETOF refcursor AS $BODY$
DECLARE
rec record;
BEGIN
OPEN ref1 FOR
SELECT * FROM table1;
RETURN NEXT ref1;
OPEN ref2 FOR
SELECT * FROM table2;
RETURN NEXT ref2;
END;
$BODY$ LANGUAGE plpgsql VOLATILE;
I want to create another function and call func1.
CREATE OR REPLACE FUNCTION script(vcodebar character varying)
RETURNS void AS
$BODY$
DECLARE
BEGIN
SELECT func1(vtoken,'details', 'amount');
FETCH ALL IN "details";
END;$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
However I receive an error:
FETCH ALL IN "details";
I can not add BEGIN ... COMMIT before call func1, because it's wrapped in a block BEGIN ... END;
How can I use 2 refcursors from the func1 ?
The cursors returned from the function are closed as soon as the call to the function completes. You should wrap the block from the SELECT statement until after the last use of the cursors in a transaction block:
BEGIN
SELECT issue_ticket(vtoken, 'details', 'amount');
FETCH ALL IN "details";
-- More statements
COMMIT; -- or ROLLBACK