It is hard to believe, but plpgsql used to be a thing. Now lost in all the hype of REST APIs and JSON wizardry, the idea of doing server-side database functions gives most people the shivers. But as it turns out, doing things server-side can be pretty useful. So useful that Postgres 11 recently upped the plpgsql game by introducing support for true stored procedures. What does that mean for you? It's time to take another look at plpgsql and what new options are available inside everyone's favorite database.
This talk aims to cover
A brief overview of postgres functions
An equally brief look at plpgsql
At least one slide on DO scripts
A slightly more extensive look at the new stored procedure functionality
A primer for advocating on using server side logic
Always with the trade-offs
Ok, plpgsql probably isn't going to take over the world, but its a handy toolset and one too many DBA's and Developers simply overlook in favor of more cumbersome solutions buried in their app code. We need to at least give it a fighting chance.
2. Robert Treat (@robtreat2) the lost art of plpgsql
whoami?
occasional
dev | ops | dba
currently
lead u.s. operations
at credativ
open source services and support
we build and run world class applications and
infrastructure to empower our clients
3. Robert Treat (@robtreat2) the lost art of plpgsql
whoami?
@robtreat2
robert.treat@credativ.us
https://www.linkedin.com/company/credativ-llc
4. Robert Treat (@robtreat2) the lost art of plpgsql
a brief overview of postgres functions
how many of you have used a postgres function?
5. Robert Treat (@robtreat2) the lost art of plpgsql
why plpgsql
server side / round trips
stable api
simplify portability*
6. Robert Treat (@robtreat2) the lost art of plpgsql
a brief overview of postgres functions
four types of postgres functions:
ā¢ internal functions
ā¢ query language functions (functions written inĀ SQL)
ā¢ procedural language functions (functions written in PL/pgSQL)
ā¢ C-language functions
they all work similarly and have overlapping bits,
today we only care about
procedural language functions
speciļ¬cally plpgsql ones
7. Robert Treat (@robtreat2) the lost art of plpgsql
a brief overview of postgres functions
https://www.postgresql.org/docs/current/sql-createfunction.html
8. Robert Treat (@robtreat2) the lost art of plpgsql
a brief overview of plpgsql
Chapter 42
āPL/pgSQLĀ -Ā SQLĀ Procedural Languageā
note: you can write stored procedures
in pure SQL, but this is not that
9. Robert Treat (@robtreat2) the lost art of plpgsql
a very brief overview of plpgsql
originally added in 6.4
(pl/tcl added in 6.3)
installed by default in 9.0
minimal language for creating triggers and user deļ¬ned functions
add basic control structures to SQL
ātrustedā language for server side computation
10. Robert Treat (@robtreat2) the lost art of plpgsql
a brief overview of plpgsql
get diagnostics
when doing dynamic query execution
āget diagnosticsā can be used for ļ¬nding
row count and call stack information
additionally, /FOUND/ variable can be used
to determine query outcomes
42.5.5 Obtaining the Result Status
11. Robert Treat (@robtreat2) the lost art of plpgsql
an example plpgsql function
CREATE OR REPLACE FUNCTION inventory_in_stock(p_inventory_id integer)
RETURNS boolean
LANGUAGE plpgsql
AS $$
DECLARE
v_rentals INTEGER;
v_out INTEGER;
BEGIN
-- AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE
-- FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED
SELECT count(*) INTO v_rentals
FROM rental
WHERE inventory_id = p_inventory_id;
IF v_rentals = 0 THEN
RETURN TRUE;
END IF;
SELECT COUNT(rental_id) INTO v_out
FROM inventory LEFT JOIN rental USING(inventory_id)
WHERE inventory.inventory_id = p_inventory_id
AND rental.return_date IS NULL;
IF v_out > 0 THEN
RETURN FALSE;
ELSE
RETURN TRUE;
END IF;
END $$;
12. Robert Treat (@robtreat2) the lost art of plpgsql
a brief overview of plpgsql
CREATE OR REPLACE FUNCTION inventory_in_stock(p_inventory_id integer)
RETURNS boolean
LANGUAGE plpgsql
AS $$
DECLARE
v_rentals INTEGER;
v_out INTEGER;
BEGIN
-- AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE
-- FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED
SELECT count(*) INTO v_rentals
FROM rental
WHERE inventory_id = p_inventory_id;
IF v_rentals = 0 THEN
RETURN TRUE;
END IF;
SELECT COUNT(rental_id) INTO v_out
FROM inventory LEFT JOIN rental USING(inventory_id)
WHERE inventory.inventory_id = p_inventory_id
AND rental.return_date IS NULL;
IF v_out > 0 THEN
RETURN FALSE;
ELSE
RETURN TRUE;
END IF;
END $$;
13. Robert Treat (@robtreat2) the lost art of plpgsql
a brief overview of plpgsql
CREATE OR REPLACE FUNCTION inventory_in_stock(p_inventory_id integer)
RETURNS boolean
LANGUAGE plpgsql
AS $$
DECLARE
v_rentals INTEGER;
v_out INTEGER;
BEGIN
-- AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE
-- FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED
SELECT count(*) INTO v_rentals
FROM rental
WHERE inventory_id = p_inventory_id;
IF v_rentals = 0 THEN
RETURN TRUE;
END IF;
SELECT COUNT(rental_id) INTO v_out
FROM inventory LEFT JOIN rental USING(inventory_id)
WHERE inventory.inventory_id = p_inventory_id
AND rental.return_date IS NULL;
IF v_out > 0 THEN
RETURN FALSE;
ELSE
RETURN TRUE;
END IF;
END $$;
14. Robert Treat (@robtreat2) the lost art of plpgsql
a brief overview of plpgsql
CREATE OR REPLACE FUNCTION inventory_in_stock(p_inventory_id integer)
RETURNS boolean
LANGUAGE plpgsql
AS $$
DECLARE
v_rentals INTEGER;
v_out INTEGER;
BEGIN
-- AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE
-- FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED
SELECT count(*) INTO v_rentals
FROM rental
WHERE inventory_id = p_inventory_id;
IF v_rentals = 0 THEN
RETURN TRUE;
END IF;
SELECT COUNT(rental_id) INTO v_out
FROM inventory LEFT JOIN rental USING(inventory_id)
WHERE inventory.inventory_id = p_inventory_id
AND rental.return_date IS NULL;
IF v_out > 0 THEN
RETURN FALSE;
ELSE
RETURN TRUE;
END IF;
END $$;
15. Robert Treat (@robtreat2) the lost art of plpgsql
an example plpgsql function
CREATE OR REPLACE FUNCTION inventory_in_stock(p_inventory_id integer)
RETURNS boolean
LANGUAGE plpgsql
AS $$
DECLARE
v_rentals INTEGER;
v_out INTEGER;
BEGIN
-- AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE
-- FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED
SELECT count(*) INTO v_rentals
FROM rental
WHERE inventory_id = p_inventory_id;
IF v_rentals = 0 THEN
RETURN TRUE;
END IF;
SELECT COUNT(rental_id) INTO v_out
FROM inventory LEFT JOIN rental USING(inventory_id)
WHERE inventory.inventory_id = p_inventory_id
AND rental.return_date IS NULL;
IF v_out > 0 THEN
RETURN FALSE;
ELSE
RETURN TRUE;
END IF;
END $$;
16. Robert Treat (@robtreat2) the lost art of plpgsql
an example plpgsql function
CREATE OR REPLACE FUNCTION inventory_in_stock(p_inventory_id integer)
RETURNS boolean
LANGUAGE plpgsql
AS $$
DECLARE
v_rentals INTEGER;
v_out INTEGER;
BEGIN
-- AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE
-- FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED
SELECT count(*) INTO v_rentals
FROM rental
WHERE inventory_id = p_inventory_id;
IF v_rentals = 0 THEN
RETURN TRUE;
END IF;
SELECT COUNT(rental_id) INTO v_out
FROM inventory LEFT JOIN rental USING(inventory_id)
WHERE inventory.inventory_id = p_inventory_id
AND rental.return_date IS NULL;
IF v_out > 0 THEN
RETURN FALSE;
ELSE
RETURN TRUE;
END IF;
END $$;
17. Robert Treat (@robtreat2) the lost art of plpgsql
an example plpgsql function
CREATE OR REPLACE FUNCTION inventory_in_stock(p_inventory_id integer)
RETURNS boolean
LANGUAGE plpgsql
AS $$
DECLARE
v_rentals INTEGER;
v_out INTEGER;
BEGIN
-- AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE
-- FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED
SELECT count(*) INTO v_rentals
FROM rental
WHERE inventory_id = p_inventory_id;
IF v_rentals = 0 THEN
RETURN TRUE;
END IF;
SELECT COUNT(rental_id) INTO v_out
FROM inventory LEFT JOIN rental USING(inventory_id)
WHERE inventory.inventory_id = p_inventory_id
AND rental.return_date IS NULL;
IF v_out > 0 THEN
RETURN FALSE;
ELSE
RETURN TRUE;
END IF;
END $$;
18. Robert Treat (@robtreat2) the lost art of plpgsql
an example plpgsql function
CREATE OR REPLACE FUNCTION inventory_in_stock(p_inventory_id integer)
RETURNS boolean
LANGUAGE plpgsql
AS $$
DECLARE
v_rentals INTEGER;
v_out INTEGER;
BEGIN
-- AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE
-- FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED
SELECT count(*) INTO v_rentals
FROM rental
WHERE inventory_id = p_inventory_id;
IF v_rentals = 0 THEN
RETURN TRUE;
END IF;
SELECT COUNT(rental_id) INTO v_out
FROM inventory LEFT JOIN rental USING(inventory_id)
WHERE inventory.inventory_id = p_inventory_id
AND rental.return_date IS NULL;
IF v_out > 0 THEN
RETURN FALSE;
ELSE
RETURN TRUE;
END IF;
END $$;
19. Robert Treat (@robtreat2) the lost art of plpgsql
an example plpgsql function
CREATE OR REPLACE FUNCTION inventory_in_stock(p_inventory_id integer)
RETURNS boolean
LANGUAGE plpgsql
AS $$
DECLARE
v_rentals INTEGER;
v_out INTEGER;
BEGIN
-- AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE
-- FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED
SELECT count(*) INTO v_rentals
FROM rental
WHERE inventory_id = p_inventory_id;
IF v_rentals = 0 THEN
RETURN TRUE;
END IF;
SELECT COUNT(rental_id) INTO v_out
FROM inventory LEFT JOIN rental USING(inventory_id)
WHERE inventory.inventory_id = p_inventory_id
AND rental.return_date IS NULL;
IF v_out > 0 THEN
RETURN FALSE;
ELSE
RETURN TRUE;
END IF;
END $$;
20. Robert Treat (@robtreat2) the lost art of plpgsql
an example plpgsql function
CREATE OR REPLACE FUNCTION inventory_in_stock(p_inventory_id integer)
RETURNS boolean
LANGUAGE plpgsql
AS $$
DECLARE
v_rentals INTEGER;
v_out INTEGER;
BEGIN
-- AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE
-- FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED
SELECT count(*) INTO v_rentals
FROM rental
WHERE inventory_id = p_inventory_id;
IF v_rentals = 0 THEN
RETURN TRUE;
END IF;
SELECT COUNT(rental_id) INTO v_out
FROM inventory LEFT JOIN rental USING(inventory_id)
WHERE inventory.inventory_id = p_inventory_id
AND rental.return_date IS NULL;
IF v_out > 0 THEN
RETURN FALSE;
ELSE
RETURN TRUE;
END IF;
END $$;
21. Robert Treat (@robtreat2) the lost art of plpgsql
an example plpgsql function
CREATE OR REPLACE FUNCTION inventory_in_stock(p_inventory_id integer)
RETURNS boolean
LANGUAGE plpgsql
AS $$
DECLARE
v_rentals INTEGER;
v_out INTEGER;
BEGIN
-- AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE
-- FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED
SELECT count(*) INTO v_rentals
FROM rental
WHERE inventory_id = p_inventory_id;
IF v_rentals = 0 THEN
RETURN TRUE;
END IF;
SELECT COUNT(rental_id) INTO v_out
FROM inventory LEFT JOIN rental USING(inventory_id)
WHERE inventory.inventory_id = p_inventory_id
AND rental.return_date IS NULL;
IF v_out > 0 THEN
RETURN FALSE;
ELSE
RETURN TRUE;
END IF;
END $$;
22. Robert Treat (@robtreat2) the lost art of plpgsql
an example plpgsql function
CREATE OR REPLACE FUNCTION inventory_in_stock(p_inventory_id integer)
RETURNS boolean
LANGUAGE plpgsql
AS $$
DECLARE
v_rentals INTEGER;
v_out INTEGER;
BEGIN
-- AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE
-- FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED
SELECT count(*) INTO v_rentals
FROM rental
WHERE inventory_id = p_inventory_id;
IF v_rentals = 0 THEN
RETURN TRUE;
END IF;
SELECT COUNT(rental_id) INTO v_out
FROM inventory LEFT JOIN rental USING(inventory_id)
WHERE inventory.inventory_id = p_inventory_id
AND rental.return_date IS NULL;
IF v_out > 0 THEN
RETURN FALSE;
ELSE
RETURN TRUE;
END IF;
END $$;
23. Robert Treat (@robtreat2) the lost art of plpgsql
an example plpgsql function
CREATE OR REPLACE FUNCTION inventory_in_stock(p_inventory_id integer)
RETURNS boolean
LANGUAGE plpgsql
AS $$
DECLARE
v_rentals INTEGER;
v_out INTEGER;
BEGIN
-- AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE
-- FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED
SELECT count(*) INTO v_rentals
FROM rental
WHERE inventory_id = p_inventory_id;
IF v_rentals = 0 THEN
RETURN TRUE;
END IF;
SELECT COUNT(rental_id) INTO v_out
FROM inventory LEFT JOIN rental USING(inventory_id)
WHERE inventory.inventory_id = p_inventory_id
AND rental.return_date IS NULL;
IF v_out > 0 THEN
RETURN FALSE;
ELSE
RETURN TRUE;
END IF;
END $$;
24. Robert Treat (@robtreat2) the lost art of plpgsql
an example plpgsql procedure
postgres@pagila=# select inventory_in_stock(42);
inventory_in_stock
--------------------
t
(1 row)
25. Robert Treat (@robtreat2) the lost art of plpgsql
how to plpgsql
user deļ¬ned functions
do scripts
stored procedures
26. postgres@pagila=#
DO $$
DECLARE
v_row record;
BEGIN
FOR v_row IN
SELECT film_id, title, rating FROM film WHERE film_id < 10
LOOP
IF v_row.rating ='G'::mpaa_rating THEN
RAISE NOTICE '% is safe for tv',v_row.title;
END IF;
END LOOP;
END$$
;
NOTICE: ACE GOLDFINGER is safe for tv
NOTICE: AFFAIR PREJUDICE is safe for tv
NOTICE: AFRICAN EGG is safe for tv
DO
Robert Treat (@robtreat2) the lost art of plpgsql
at least one slide on DO scripts
https://www.postgresql.org/docs/current/sql-do.html
27. postgres@pagila=#
DO$$
BEGIN
vacuum actor;
vacuum film;
END
$$;
ERROR: VACUUM cannot be executed from a function
CONTEXT: SQL statement "vacuum actor"
PL/pgSQL function inline_code_block line 1 at SQL statement
Robert Treat (@robtreat2) the lost art of plpgsql
at least one slide on DO scripts
30. Robert Treat (@robtreat2) the lost art of plpgsql
what even is a procedure?
āother databases sayā
functions: user deļ¬ned code that executes some
set of commands and returns a result
stored procedures: user deļ¬ned code that
executes some set of commands returning no
result
31. Robert Treat (@robtreat2) the lost art of plpgsql
what even is a procedure?
create function
smored_promedure()
returns void as $$
begin
return;
end
$$ language plpgsql;
postgres@pagila=#
select smored_promedure();
smored_promedure
------------------
(1 row)
32. Robert Treat (@robtreat2) the lost art of plpgsql
what even is a procedure?
āin postgres, functions are equivalent to stored
procedures, and can be used interchangeablyā
33. Robert Treat (@robtreat2) the lost art of plpgsql
stored procedures, turned up to 11
sql standard based
allow transaction control
34. Robert Treat (@robtreat2) the lost art of plpgsql
stored procedures, turned up to 11
postgres@pagila=# h create procedure
Command: CREATE PROCEDURE
Description: define a new procedure
Syntax:
CREATE [ OR REPLACE ] PROCEDURE
name ( [ [ argmode ] [ argname ] argtype [ { DEFAULT | = }
default_expr ] [, ...] ] )
{ LANGUAGE lang_name
| TRANSFORM { FOR TYPE type_name } [, ... ]
| [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
| SET configuration_parameter { TO value | = value | FROM CURRENT }
| AS 'definition'
| AS 'obj_file', 'link_symbol'
} ...
35. Robert Treat (@robtreat2) the lost art of plpgsql
stored procedures, turned up to 11
postgres@pagila=# h call
Command: CALL
Description: invoke a procedure
Syntax:
CALL name ( [ argument ] [, ...] )
36. Robert Treat (@robtreat2) the lost art of plpgsql
an example plpgsql procedure
CREATE OR REPLACE PROCEDURE inventory_in_sproc(p_inventory_id integer)
LANGUAGE plpgsql
AS $$
DECLARE
v_rentals INTEGER;
v_out INTEGER;
BEGIN
-- AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE
-- FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED
SELECT count(*) INTO v_rentals
FROM rental
WHERE inventory_id = p_inventory_id;
IF v_rentals = 0 THEN
RAISE NOTICE āTRUEā;
END IF;
SELECT COUNT(rental_id) INTO v_out
FROM inventory LEFT JOIN rental USING(inventory_id)
WHERE inventory.inventory_id = p_inventory_id
AND rental.return_date IS NULL;
IF v_out > 0 THEN
RAISE NOTICE āFALSEā;
ELSE
RAISE NOTICE āTRUEā;
END IF;
END $$;
37. Robert Treat (@robtreat2) the lost art of plpgsql
an example plpgsql procedure
CREATE OR REPLACE PROCEDURE inventory_in_sproc(p_inventory_id integer)
LANGUAGE plpgsql
AS $$
DECLARE
v_rentals INTEGER;
v_out INTEGER;
BEGIN
-- AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE
-- FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED
SELECT count(*) INTO v_rentals
FROM rental
WHERE inventory_id = p_inventory_id;
IF v_rentals = 0 THEN
RAISE NOTICE āTRUEā;
END IF;
SELECT COUNT(rental_id) INTO v_out
FROM inventory LEFT JOIN rental USING(inventory_id)
WHERE inventory.inventory_id = p_inventory_id
AND rental.return_date IS NULL;
IF v_out > 0 THEN
RAISE NOTICE āFALSEā;
ELSE
RAISE NOTICE āTRUEā;
END IF;
END $$;
38. Robert Treat (@robtreat2) the lost art of plpgsql
an example plpgsql procedure
postgres@pagila=# call inventory_in_sproc(42);
NOTICE: TRUE
CALL
postgres@pagila=# select inventory_in_stock(42);
inventory_in_stock
--------------------
t
(1 row)
39. Robert Treat (@robtreat2) the lost art of plpgsql
an example plpgsql procedure
CREATE OR REPLACE PROCEDURE inventory_in_shock(
IN p_inventory_id integer,
INOUT p_instock boolean DEFAULT false
) LANGUAGE plpgsql
AS $$
DECLARE
v_rentals INTEGER;
v_out INTEGER;
BEGIN
-- AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE
-- FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED
SELECT count(*) INTO v_rentals
FROM rental
WHERE inventory_id = p_inventory_id;
IF v_rentals = 0 THEN
p_instock := TRUE;
END IF;
SELECT COUNT(rental_id) INTO v_out
FROM inventory LEFT JOIN rental USING(inventory_id)
WHERE inventory.inventory_id = p_inventory_id
AND rental.return_date IS NULL;
IF v_out > 0 THEN
p_instock := FALSE;
ELSE
p_instock := TRUE;
END IF;
END $$;
40. Robert Treat (@robtreat2) the lost art of plpgsql
an example plpgsql procedure
postgres@pagila=# call inventory_in_sproc(42);
NOTICE: TRUE
CALL
postgres@pagila=# select inventory_in_stock(42);
inventory_in_stock
--------------------
t
(1 row)
postgres@pagila=# call inventory_in_shock(42);
p_instock
-----------
t
(1 row)
41. Robert Treat (@robtreat2) the lost art of plpgsql
plpgsql procedure transaction control
postgres@pagila=# create table xx (xx int);
CREATE TABLE
postgres@pagila=#
create or replace procedure xx()
as $$
begin
insert into xx select 1; rollback;
insert into xx select 2; commit;
insert into xx select 3; rollback;
end $$
language plpgsql;
CREATE PROCEDURE
postgres@pagila=# call xx();
CALL
postgres@pagila=# select * from xx;
xx
----
2
(1 row)
42. Robert Treat (@robtreat2) the lost art of plpgsql
plpgsql procedure transaction control
postgres@pagila=# create or replace procedure merry_maids() as $$
begin vacuum actor; end $$ language plpgsql;
CREATE PROCEDURE
postgres@pagila=# call merry_maids();
ERROR: VACUUM cannot be executed from a function
CONTEXT: SQL statement "vacuum actorā
* fyi, you can do this with analyze
43. Robert Treat (@robtreat2) the lost art of plpgsql
more to do!
certain ddl - create index concurrently
vacuum support
multiple result-sets