Testing PostgreSQL functions that consume and return refcursor - sql

I want to test results of a Postgres function (changing the function is not a possibility).
The function receives as arguments a REFCURSOR and several other things and returns the same RECURSOR.
get_function_that_returns_cursor(ret, 4100, 'SOMETHING', 123465)
Now I want to create a small test in Postgres to get the results of this FUNCTION.
Something Like the code below (this is my approach but it is not working):
DO $$ DECLARE
ret REFCURSOR;
row_to_read table_it_will_return%ROWTYPE ;
BEGIN
PERFORM get_function_that_returns_cursor(ret, 4100, 'SOMETHING', 123465);
-- OR SELECT get_function_that_returns_cursor(ret, 4100, 'SOMETHING', 123465) INTO ret
FOR row_to_read IN SELECT * FROM ret LOOP
-- (...)
RAISE NOTICE 'Row read...';
END LOOP;
CLOSE ret;
END $$;
Any suggestion on how to get this to work? A generic solution that can be used for testing this type of functions (that get a Cursor and return a Cursor?
And if we don't know the rowtype that is being returned how could we do it?

Q1
Your "small test" can be plain SQL:
BEGIN;
SELECT get_function_that_returns_cursor('ret', 4100, 'foo', 123); -- note: 'ret'
FETCH ALL IN ret; -- works for any rowtype
COMMIT; -- or ROLLBACK;
Execute COMMIT / ROLLBACK after you inspected the results. Most clients only display the result of the lat command.
More in the chapter Returning Cursors of the manual.
Q2
And if we don't know the rowtype that is being returned how could we do it?
Since you only want to inspect the results, you could cast the whole record to text.
This way you avoid the problem with dynamic return types for the function altogether.
Consider this demo:
CREATE TABLE a (a_id int PRIMARY KEY, a text);
INSERT INTO a VALUES (1, 'foo'), (2, 'bar');
CREATE OR REPLACE FUNCTION reffunc(INOUT ret refcursor)
LANGUAGE plpgsql AS
$func$
BEGIN
OPEN ret FOR SELECT * FROM a;
END
$func$;
CREATE OR REPLACE FUNCTION ctest()
RETURNS SETOF text
LANGUAGE plpgsql AS
$func$
DECLARE
curs1 refcursor;
rec record;
BEGIN
curs1 := reffunc('ret'); -- simple assignment
LOOP
FETCH curs1 INTO rec;
EXIT WHEN NOT FOUND; -- note the placement!
RETURN NEXT rec::text;
END LOOP;
END
$func$;
db<>fiddle here
Old sqlfiddle

This worked for what I wanted:
DO $$ DECLARE
mycursor REFCURSOR;
rec RECORD;
BEGIN
SELECT 'ret' INTO mycursor FROM get_function_that_returns_cursor('ret'::REFCURSOR, 4100, 'SOMETHING', 123465);
WHILE (FOUND) LOOP
FETCH mycursor INTO rec;
RAISE NOTICE 'Row read. Data: % ', rec.collumn_name;
END LOOP;
END $$

Related

How do I execute a SELECT query in a text string?

I have a function my_funct(param1,param2) returning a SELECT QUERY as a text, I usually C/c the result, remove quotes, then run it.
How can execute the returned text in a query that call the function ?
I expect something like this:
SELECT resultOf( myfunct('foo','bar'))
with no extra columnname/type to declare. If such function does not exits built-in, let's create it, I don't mind.
If I understood correctly you want
Call a function returning a select query as text
Run that query to get the result.
Number and type of columns are dynamic
1. Using Do Block:
DO
$$
declare
ref_cursor refcursor:='mycursor';
begin
open ref_cursor for execute (select * from my_funct('foo','bar')) ;
end;
$$;
--Fetch all data from cursor
Fetch all from mycursor;
2. Using Function returning refcursor:
You can create a function like below:
create function my_funct1(param1 text) returns refcursor as
$$
declare
ref_cursor refcursor:='mycursor';
begin
open ref_cursor for execute param1;
return ref_cursor;
end;
$$
language plpgsql
To call above function use following code:
begin ;
select my_funct1((select * from my_funct('foo','bar')) );
fetch all from mycursor;
commit;
DEMO

How can I return a SELECT query with an oracle function?

I need to create a function that allows me to return the same result as a SELECT query and that contains pl/sql code.
I tried something really simple :
create or replace FUNCTION test
RETURN SYS_REFCURSOR
IS
l_rc SYS_REFCURSOR;
BEGIN
OPEN l_rc
FOR SELECT *
FROM my_table;
RETURN l_rc;
END;
But when I call my function with SELECT test from dual;, I get all result from my_table in a single cell instead of having each columns separated.
Is there a way of doing what I want ?
Ideally, I want a view but there seems to be no way of adding logical conditions with them.
the function has to be pipelined. For example :
TYPE MyType IS RECORD(ID NUMBER);
TYPE MyTableType IS TABLE OF MyType;
Function MyFunction(Arguments) return MyTableType pipelined is
Cursor Cur is select * from whetever;
R Cur%rowtype;
Begin
Open cur;
loop
fetch Cur into R;
exit when Cur%notfound;
pipe row(R);
End loop;
Close cur;
End MyFunction;
Then you can call it via :
select * from table(MyFunction(Arguments));
The simplest way is to leave the function as it is and only call it properly:
create table my_table (id, memo) as
select 1, 'some memo' from dual
/
create or replace function MyTableById (id int) return sys_refcursor is
rc sys_refcursor;
begin
open rc for
select * from my_table where id=MyTableById.id;
return rc;
end;
/
var rc refcursor
exec :rc := MyTableById (1);
print rc
ID MEMO
---------- ---------
1 some memo

For loops in postgresql doesnt run on function, but can run outside function

Some one please help me on this problem. I'm using postgresql ver 12. Thank you. Please read the comment on the result. Running from function doesn't work. so weird.
-- NOT WORKING
CREATE OR REPLACE FUNCTION mefunc()
RETURNS TRIGGER AS $BODY$
DECLARE
a RECORD;
BEGIN
FOR a IN SELECT "aa" FROM medata LOOP
INSERT INTO othermedata ("id", "aa") VALUES ('1', a.aa);
END LOOP;
RETURN NEW;
END
$BODY$ LANGUAGE plpgsql;
when i raise notice the value of a i get ()
WORKING
DO $$
DECLARE
a RECORD;
BEGIN
FOR a IN SELECT "aa" FROM medata LOOP
INSERT INTO othermedata ("id", "aa") VALUES ('1', a.aa);
END LOOP;
END $$
When I raise notice the value of a I get output as "someoutputdata"
Try this. You should return new record when using Trigger. As you are not returning any value so it is returning null value. I have tested the below code and it is working fine.
CREATE OR REPLACE FUNCTION mefunc()
RETURNS TRIGGER AS $BODY$
DECLARE
a RECORD;
BEGIN
FOR a IN SELECT aa FROM metadata loop
INSERT INTO othermedata (id, aa) VALUES ('1', a.aa);
END LOOP;
RETURN new;
END
$BODY$ LANGUAGE plpgsql;

Explain - inserts only one row

I'm trying to manually save optimizer plan for further analysis, like this:
do $$
declare
tmp text;
begin
explain
select * from public.some_table where 1=2 into tmp;
insert into public.plans(plan) values (tmp);
end; $$
But when I select it later, I see it only saved first row from the explain statement:
Result (cost=0.00..82.97 rows=1 width=114)
How can I make it to save the whole plan?
Because explain cannot be used like e.g. a SELECT this is a bit tricky and you need dynamic SQL for this.
The following worked for me:
do
$$
declare
plan_line record;
begin
for plan_line in execute 'explain select * from public.some_table where 1=2' loop
insert into plans values (plan_line."QUERY PLAN");
end loop;
end;
$$
Having the statement to be explained in a string makes things a bit more complicated.
If I needed that on a regular basis, I would probably create a function that does this:
create or replace function explain(to_explain text)
returns setof text
as
$$
declare
plan_line record;
begin
for plan_line in execute 'explain '||to_explain loop
return next plan_line."QUERY PLAN";
end loop;
end;
$$
language plpgsql;
Then you can do something like:
insert into plans
select *
from explain('select ...');

How to do a batch commit in plpgsql?

When I do the below function, some errors occurs:
**ddddl=# select sssss(1,10);
ERROR: cannot begin/end transactions in PL/pgSQL
HINT: Use a BEGIN block with an EXCEPTION clause instead.
CONTEXT: PL/pgSQL function sssss(integer,integer) line 8 at SQL statement**
Here is my sample code.
CREATE OR REPLACE FUNCTION sssss(
IN c_1 int,
IN f_i int
) returns void as
$$
DECLARE t_c INT;
BEGIN
t_c := f_i;
WHILE c_1 <= t_c
loop
IF MOD(c_1, 4) = 1 THEN
start transaction;
END IF;
-- My statements here.
IF MOD(c_1, 4) = 0 THEN
COMMIT;
END IF;
c_1 := c_1 + 1;
END loop;
COMMIT;
END;
$$ language plpgsql;
So could anyone give me a sample of how to finish my job as the above code?
I'm using PostgreSQL 9.2.
In general, I think there are three approaches you can take:
Do without. Just let the whole thing be committed at once, like PostgreSQL wants.
Write a wrapper in application code that sets up a transaction and calls your function. (This is simple to do in a shell script, for example.)
Use a hack. One hack that exists is, you can have your function create a database-link to your own database, and perform subtransactions via remote sessions. [More information.] (This is essentially equivalent to the "wrapper in application code" approach, except that the application code is still in PL/pgSQL.)
From Postgresql 12 onwards you can do batch commits if you use procedures instead of functions, invoked by CALL command. Therefore you're function will be rewritten like below and invoked with CALL command:
CREATE OR REPLACE PROCEDURE sssss(IN c_1 int,IN f_i int)
LANGUAGE plpgsql
AS $$
DECLARE
t_c INT;
BEGIN
t_c := f_i;
WHILE c_1 <= t_c
loop
-- My statements here.
IF MOD(c_1, 4) = 0 THEN
COMMIT;
END IF;
c_1 := c_1 + 1;
END loop;
COMMIT;
END;
$$;
CALL sssss(1,10);
More details about transaction management regarding Postgres are available here:
https://www.postgresql.org/docs/12/plpgsql-transactions.html