Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

the-lost-art-of-plpgsql

669 views

Published on

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.

Published in: Technology
  • Be the first to comment

the-lost-art-of-plpgsql

  1. 1. The Lost Art of PLPGSQL pgcon 2019 Robert Treat (@robtreat2) the lost art of plpgsql NOTE: There Will Be Code!
  2. 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. 3. Robert Treat (@robtreat2) the lost art of plpgsql whoami? @robtreat2 robert.treat@credativ.us https://www.linkedin.com/company/credativ-llc
  4. 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. 5. Robert Treat (@robtreat2) the lost art of plpgsql why plpgsql server side / round trips stable api simplify portability*
  6. 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 specifically plpgsql ones
  7. 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. 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. 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 defined functions add basic control structures to SQL “trusted” language for server side computation
  10. 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 finding row count and call stack information additionally, /FOUND/ variable can be used to determine query outcomes 42.5.5 Obtaining the Result Status
  11. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 25. Robert Treat (@robtreat2) the lost art of plpgsql how to plpgsql user defined functions do scripts stored procedures
  26. 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. 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
  28. 28. Robert Treat (@robtreat2) the lost art of plpgsql what even is a procedure? enter stored procedures
  29. 29. Robert Treat (@robtreat2) the lost art of plpgsql what even is a procedure? but first a history
  30. 30. Robert Treat (@robtreat2) the lost art of plpgsql what even is a procedure? “other databases say” functions: user defined code that executes some set of commands and returns a result stored procedures: user defined code that executes some set of commands returning no result
  31. 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. 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. 33. Robert Treat (@robtreat2) the lost art of plpgsql stored procedures, turned up to 11 sql standard based allow transaction control
  34. 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. 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. 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. 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. 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. 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. 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. 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. 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. 43. Robert Treat (@robtreat2) the lost art of plpgsql more to do! certain ddl - create index concurrently vacuum support multiple result-sets
  44. 44. #thankyou Robert Treat (@robtreat2) the lost art of plpgsql

×