Příručka SQL/PSM
Úvod do SQL/PSM (PL/pgPSM)
Začátkem devadesátých let minulého století začalo být jasné, že standard ANSI SQL postrádá prostředky pro tvorbu uložených procedur (zejména možnost deklarovat proměnné a dále s nimi pracovat, řízení toku - cykly a podmínky). Na tuto skutečnost reagovaly komerční firmy implementací vlastních proprietárních prostředí. Zřejmě nejpopulárnějšími v té době byly jazyky PL/SQL (Oracle, 1992), T-SQL (Sybase a Microsoft, 1995) a SPL (Informix, 1996). Od roku 1990 se v standardizační komisi ANSI SQl této problematice začala věnovat skupina vývojářů kolem Jima Meltona. Nejstarší zmínka o SQL/PSM je ze srpna roku 1994. (Prvním výstupem bylo PSM-96 (PSM - Persistent Stored Modules) postavené nad SQL92. Ve svém článku Budoucnost programování v SQL z ledna 1997 zmiňuje Joe Celko SQL/PSM jako jazyk vycházející z Algolu rozšířený o prvky Ady (řešení výjimek). V roce 1998 (draft) standard byl již součástí SQL3 a označen současným názvem SQL/PSM (ANSI/ISO/IEC 9075-4:1999). Bohužel v té době většina významných firem již měla své vlastní prostředí (nekompatibilní se standardem), které, jak se ukázalo později, neopustily. Standard byl implementován pouze v těch RDBMS, kde do roku 1998 nebyla možnost tvorby SQL uložených procedur. Kromě DB2 (SQL PL, IBM, 2001) se většinou jedná o minoritní RDNMS: Mimer, Solid, 602SQL server. Do popředí se SQL/PSM opět dostává po roce 2005, kdy je implementován v Advantage Database Serveru (Sybase iAnywhere, 2005), v MySQL (2005) a PostgreSQL (2007). Jen zřídka je SQL/PSM implementován v plném rozsahu. Za zdařilou implementaci se pokládá SQL PL v DB2. Implementace SQL/PSM v PostgreSQL se nazývá (jak je v PostgreSQL zvykem) PL/pgPSM.
Stručně lze programovací jazyk definovaný v SQL/PSM charakterizovat jako jednoúčelový, zcela nový, moderní, jednoduchý procedurální programovací jazyk s integrovaným SQL a úzkou vazbou na prostředí SQL serverů. Jedná se o jazyk s bohatým repertoárem řídících konstrukcí a komfortním modelem zachycení a zpracování chyb. Datové typy, funkce přebírá z hostitelského SQL serveru. I/O rutiny pak úplně chybí (přesně v duchu architektury uložených procedur). Standard SQL/PSM se nevěnuje pouze popisu SQL procedur. Obsahuje i popis uspořádání procedur do modulů a popis tzv. externích procedur (implementace procedur v dalších prg. jazycích, např. C, Cobol, atd). Přestože se jedná o zcela nový programovací jazyk, na celé řadě konstrukcí je zřejmá inspirace jazyky PL/1, jazyky ADA, Modula a dalšími jazyky.
Následující ukázka je modifikací klasického příkladu Hello world v prostředí SQL/PSM. Parametrem funkce je jedinečný identifikátor uživatele. Uvnitř funkce se na základě tohoto identifikátoru získá skutečné jméno a příjmení uživatele z tabulky Users. Všimněte si deklarace proměnné, plnění proměnné hodnotou z tabulky (integrace SQL příkazu SELECT), použití SQL datového typu (varchar) a SQL oparátoru (operátor ||), komentáře.
CREATE OR REPLACE FUNCTION hello(uid integer) RETURNS varchar AS $$ BEGIN DECLARE real_name varchar; -- Get real name SET real_name = (SELECT name || ' ' || surname FROM Users WHERE Users.uid = hello.uid); RETURN 'Hello, ' || real_name; END; $$ LANGUAGE plpgpsm; SELECT hello(123);
Vložení zdrojového kódu funkce mezi dvojici symbolů $$ je specifické pro PostgreSQL. PL/pgPSM je z pohledu PostgreSQL externí programovací jazyk, jako ostatní podporované PL jazyky (PL/pgSQL, PL/Perl, PL/Python), a tudíž PostgreSQL k tomuto jazyku přistupuje stejně a vynucuje si stejná pravidla zápisu. Implementace SQL/PSM má ještě další dvě podstatné odlišnosti od standardu. Za prvé, PostgreSQL umožňuje definovat pouze funkce (nikoliv procedury), což se projevuje určitými omezeními v řízení transakcí. Za druhé, PostgreSQL implicitně spouští funkce v režimu SECURITY CALLER, kdežto standard předpokládá režim SECURITY DEFINER. Mezi standardem a implementací SQL/PSM v PostgreSQL je ještě několik dalších rozdílů daných určitou volností standardu a implementačními závislostmi, nicméně nejedná se o nijak zvlášť markantní rozdíly. Asi je ještě předčasné počítat s plnou přenositelností kódu mezi RDBMS podporujícími SQL/PSM, nicméně v celé řadě případů sou jsou nutné úpravy minimální. Problémy mohou nastat u normou neošetřených konstrukcí (unbound selects, multirecordsets, atd).
Jazyk SQL/PSM je v RDBMS PostgreSQL (PL/pgPSM) běží na modifikovaném run-time PL/pgSQL. Tudíž výkonnostně i funkčně je na tom velice podobně jako jazyk PL/pgSQL. PL/pgSQL je v tuto chvíli vyzrálejší a časem prověřené prostředí vhodné pro jakkoliv kritické aplikace. PL/pgPSM naopak přináší shodu se standardem a novější, a bohatší programovací jazyk.
Rozlišujeme mezi tzv. externími uloženými procedurami a SQL uloženými procedurami. Rozdíl je v použitém jazyce a v přístupu. Pokud uložené procedury jsou realizovány v klasických prg. jazycích, a běží mimo prostředí SQL serveru (vlastní funkce, vlastní datové typy), jedná se o externí procedury. V případě, že se použije specializovaný jazyk a procedura běží v prostředí SQL serveru (sdílí datové typy a funkce), pak takovou proceduru označujeme jako SQL proceduru. Příklady SQL procedur jsou prostředí PL/SQL, T-SQL, nebo PSQL či PL/pgSQL, PL/pgPSM. Externí procedury jsou standardizované pouze pro jazyk Java (SQL/J). Existuje ovšem celá řada dalších implementací např. CLR pro SQLServer 2005 nebo PL/Perl, PL/Python pro PostgreSQL. Obě třídy procedur mají své pro a proti, doporučuje se ale upřednostnění SQL procedur vyjma těch případů, kdy jsou neefektivní (např. iterační výpočet integrálu) nebo chybí dostatečná funkcionalita (např. I/O operace). Velkou výhodou SQL procedur je integrace s prostředím. Zpravidla nedochází k zbytečným konverzím, a obvykle se používají předzpracované SQL příkazy (prepared statements). Kromě toho všechny statické SQL příkazy jsou verifikované (což ještě neznamená, že jsou 100% správné, nicméně to znamená, že jsou syntakticky správné).
Praktické tipy návrhu uložených procedur v prostředí PL/pgPSM
V podstatě platí veškerá doporučení pro návrh jakéhokoliv software, tj. požadavky na čitelnost, srozumitelnost kódu:
- dbejte na dodržování jmenné konvence,
- používejte komentáře a názorné názvy proměnných,
- používejte jednotnou konvenci pro odsazování bloků,
- dodržujte rozumnou délku procedury (kolem 50 řádků),
- používejte "upovídaný" styl .. využívejte volitelná návěstí smyček.
K tomu ještě specifická doporučení platná pouze pro SQL uložené procedury (bez ohledu na konkrétní prostředí):
- pomocí prefixů a kvalifikovaných jmen atributů se vyhněte možným kolizím názvů sloupců a jmen proměnných,
- jednotným způsobem řešte zachycení a ošetření chyb,
- vyhněte se ISAM programování - co lze vyřešit pomocí SQL řešte pomocí SQL (pozor na cykly napříč celými tabulkami),
- omezte kurzory a dočasné tabulky na nezbytné minimum,
- v triggerech neopravujte data,
- procedury by měli realizovat určitou činnost, nikoliv jen zapouzdřit selecty.
Na následujících dvou příkladech si všimněte špatného (ISAM) přístupu a dobrého přístupu k návrhu uložených procedur. Obě procedury volají pro určitou podmnožinu zaměstnanců proceduru print_info.
-- spatne (ISAM pristup) CREATE OR REPLACE FUNCTION report_a() RETURNS void AS $$ main: FOR outer AS SELECT id FROM Users DO BEGIN DECLARE name, surname varchar; DECLARE age int; SET (name, surname, age) = (SELECT e.name, e.surname, e.age FROM Employers e WHERE e.id = outer.id); IF age >= 20 AND age <= 29 THEN CALL print_info(name, surname, age, 'y'); ELSE IF age >=30 AND age <= 50 THEN CALL print_info(name, surname, age, 'o'); END IF; END; END FOR main; $$ LANGUAGE plpgpsm; -- dobre CREATE OR REPLACE FUNCTION report_b() RETURNS void AS $$ main: FOR fc AS -- veskere mozne podminky a transformace resim v prikazu SELECT SELECT e.*, CASE WHEN e.age BETWEEN 20 AND 29 THEN 'y' WHEN e.age BETWEEN 30 AND 50 THEN 'o' END AS tp FROM Employers e WHERE e.age BETWEEN 20 AND 50 DO CALL print_info(fc.name, fc.surname, fc.age, fc.tp); END FOR main; $$ LANGUAGE plpgpsm;
Příručka jazyka PL/pgPSM
Každá funkce obsahuje jeden PL/pgPSM příkaz. Následující jedno příkazové funkce jsou korektní. Pozn. Pro uživatele PL/SQL jazyků to může být mírný šok. Na rozíl od PL/SQL, kde základním příkazem funkce nebo procedury je složený příkaz, je v SQL/PSM základním příkazem libovolný příkaz. Pokud funkce neobsahuje žádný SQL příkaz, používejte atribut IMMUTABLE. Kromě jiného i zajistíte efektivnější provádění funkce. Speciálním PL/pgPSM příkazem je složený příkaz, který umožňuje zadat libovolně dlouhou syntakticky správnou posloupnost PL/pgPSM příkazů.
CREATE OR REPLACE FUNCTION sum2params(IN a integer, IN b integer, OUT c integer) AS $$ SET c = a + b; $$ LANGUAGE plpgpsm IMMUTABLE; CREATE OR REPLACE FUNCTION insert_val(a integer) RETURNS void AS $$ INSERT INTO Foo VALUES(a); $$ LANGUAGE plpgpsm; CREATE OR REPLACE FUNCTION get_sum(IN a integer, OUT b integer) AS $$ SET b = (SELECT sum(f.a) FROM Foo f WHERE f.a > get_sum.a); $$ LANGUAGE plpgpsm; CREATE OR REPLACE FUNCTION dummy() RETURNS void AS $$ BEGIN END; $$ LANGUAGE plpgpsm IMMUTABLE;
K dispozici jsou následující příkazy:
- SQL příkaz,
- složený příkaz - mezi dvojicí BEGIN a END je libovolný počet PL/pgPSM příkazů oddělených středníkem,
- Volání a ukončení procedury - příkazy CALL a RETURN,
- přiřazovací příkaz - SET proměnná = hodnota,
- Podmíněné provádění příkazů:
- Příkazy cyklu
- LOOP co END LOOP,
- WHILE podmínka DO co END WHILE,
- REPEAT co UNTIL podmínka END REPEAT,
- FOR select DO co END FOR (iterace napříč výsledkem dotazu).
- signalizace chyb - SIGNAL a RESIGNAL
- zobrazení výsledku na konzoli - PRINT (nestandardní konstrukce),
- opuštění a nová iterace cyklu - příkazy LEAVE a ITERATE,
- dynamické SQL - EXECUTE a EXECUTE IMMEDIATE,
- Příkaz GET DIAGNOSTICS - získání diagnostických údajů.
Ještě před samotným popisem PL/pgPSM příkazů si projděte následující jednoduché ukázky:
-- case statement CREATE OR REPLACE FUNCTION foo1(a integer) RETURNS void AS $$ CASE a WHEN 1, 3, 5, 7, 9 THEN PRINT a, 'is odd number'; WHEN 2, 4, 6, 8, 10 THEN PRINT a. 'is odd number'; ELSE PRINT a, 'isn't from range 1..10'; END CASE; $$ LANGUAGE plpgpsm; -- while statement CREATE OR REPLACE FUNCTION foo2(a integer) RETURNS void AS $$ BEGIN DECLARE i integer DEFAULT 1; WHILE i <= a DO PRINT i; SET i = i + 1; END WHILE; END $$ LANGUAGE plpgpsm; -- for statement CREATE OR REPLACE FUNCTION foo3(a integer) RETURNS void AS $$ FOR fc AS SELECT i FROM generate_series(1,a) AS g(i) DO PRINT fc.i; END FOR; $$ LANGUAGE plpgpsm;
Použití jazyka PL/pgPSM
- Použití kurzorů
- Funkce vracející tabulky
- Ošetření chyb, signalizace chyby (výjimky) a zachycení signálu
- Použití dočasných tabulek v PL/pgPSM
Parametry ovlivňující provádění funkcí
V PL/pgPSM jsou k dispozici dva přepínače: DUMP a RECOMPILE. Použití prvního způsobí výpis přeloženého kódu funkce do systémového logu. Druhý zajistí vynulování nakešovaných prováděcích plánů při každém startu funkce. Parametry se zapisují ještě před vlastní kód za klíčové slovo #OPTION.
#OPTION DUMP
Účelem tohoto přepínače je zobrazení přeloženého kódu do systémováho logu. Rozhodně se nejedná o typickou činnost - většina uživatelů PL/pgSQL o této možnosti v životě neslyšela. Dokáže být velice užitečný při hledání kolize názvů proměnných a SQL atributů. V případě této chyby nám systém hlásí (v lepším případě), že dotaz nelze přeložit nebo (v horším případě) je výsledek dotazu jiný než očekáváme.
Tento přepínač demonstruje skutečnost, že PL/pgSQL je v podstatě preprocesor jazyka SQL. Vlastní interpret je natolik minimalistický, že pro provádění veškerých operací (logických, aritmetických) se spoléhá na SQL. Díky tomu je zajištěna kompatibilita s SQL. Na druhou stranu PL/pgSQL se nehodí pro výpočetně náročné úlohy (s provedením každého SQL příkazu je spojená nezanedbatelná režie). K těmto účelům v PostgreSQL slouží PL/Perl, PL/Python nebo klasické C.
-- tabulka foo obsahuje sloupec a CREATE OR REPLACE FUNCTION kolize(a integer) RETURNS SETOF Foo AS $$ #option dump SELECT * FROM Foo WHERE a = a; $$ LANGUAGE plpgpsm;
Jedná se o skutečně záludnout chybu (a poměrně častou). Přeložený kód výpadá následovně:
Execution tree of successfully compiled PL/pgSQL function kolize(integer): Function's data area: entry 0: VAR $1 type int4 (typoid 23) atttypmod -1 entry 1: VAR found type bool (typoid 16) atttypmod -1 Function's statements: 0: *unnamed*: BLOCK 2: SQL 'SELECT * FROM Foo WHERE $1 = $1 {$1=0}' 0: RETURN NULL END *unnamed* End of execution tree of function kolize(integer)
Chyba je v podmínce WHERE. PL/pgSQL nechápe zápis a = a jako podmínku typu: SQL atribut je roven proměnné. Zápis znamená, že proměnná a je rovna proměnné a, což je splněno pro všechny řádky tabulky, a také výsledkem je celá tabulka Foo.
#OPTION RECOMPILE
Tento přepínač snižuje pravděpodobnost nekonzistence prováděcích plánů tím, že před každým startem procesury se procedura znovu přeloží a tím dojde k vyčištění cache prováděcích plánů. Zároveň se tím ale prodlouží doba spuštění procedury (dochází k překladu) a doba běhu (dochází k průbežnému vytváření prováděcích plánů). Tudíž tento přepínač používejte jen v nutných případech, nebo ještě lépe, nepoužívejte jej vůbec. Jak se vyhnout použití přepínače RECOMPILE je popsáno v sekci věnované dočasným tabulkám.Účelem tohoto přepínače je usnadnit portování uložených procedur z jiných RDBMS, kde jinak funguje ukládání prováděcích plánů.
CREATE OR REPLACE FUNCTION testr() RETURNS SETOF Foo AS $$ #option dump recompile BEGIN DROP TABLE IF EXISTS FooG; INSERT INTO FooG SELECT * FROM Foo; RETURN TABLE(SELECT * FROM FooG); END; $$ LANGUAGE plpgpsm;
Tento přepínač je analogií přepínači WITH RECOMPILE v T-SQL Microsoft SQL Serveru.
Portování uložených procedur z MySQL 5.x
Originální zdrojový kód procedury generující fraktály je přebrán z archivu MySQL dev. PostgreSQL řeší jinak:
- změnu aktuálního schématu,
- zápis zdrojového kódu procedury (nepoužívá mechanismus separátoru),
- pro spojení řetězců používá operátor || nikoliv funkci concat,
- neumožňuje volné SQL dotazy, jelikož nepodporuje procedury, ale pouze funkce.
Všimněte si minimálních rozdílů v upraveném kódu pro PostgreSQL.
Instalace PL/pgPSM
Vzhledem k nezralosti implementace PL/pgPSM ještě tento interpret není zařazen do distribuce. Zatím je ke stažení v jeslích postgresql projektů http://pgfoundry. Přesun do distribuce bude možný až po plné implementaci standardu a po důkladném otestování. Zatím zbývá dopsat podporu příkazů RESIGNAL a GET STACKET DIAGNOSTIC. Pokud se obejdete bez těchto příkazů, můžete používat PL/pgPSM bez obav. Interpret PL/pgPSM je modifikací interpretu PL/pgSQL, který je lety i tisíci projekty důkladně prověřen.
Pro překlad PL/pgPSM je potřeba instalovat PostgreSQL ze zdrojových kódů. Potřebujete alespoň verzi 8.2. Poslední verzi PL/pgPSM naleznete na http://pgfoundry.org/frs/?group_id=1000238&release_id=767.
Postup
- Stáhněte si nejnovější zdrojové soubory PL/pgPSM z adresáře http://pgfoundry.org/frs/?group_id=1000238
- pokud máte PostgreSQL instalovaný ze zdrojových kódů, rozbalte archív do adresáře contrib
- proveďte příkazy make a make install
- jako superuser spusťe psql postgres < plpgpsm.sql
- od tohoto okamžiku můžete používat plpgpsm stejně jako ostatní jazyky
- test instalace make installcheck
Pokud nemáte PostgreSQL instalovaný ze zdrojových kódů, musíte mít alespoň develop knihovny PostgreSQL
- kdekoliv rozbalte archiv plpgpsm
- spusťte příkazy make USE_PGXS=1 a make USE_PGXS=1 install
- další postup je stejný jako v předchozím případě
Uvítáme jakoukoliv formu spolupráce. A to ať doplněním této dokumentace, její korekturou, překladem, rozšířením testovacích scénářů, doplněním funkcionality nebo samotným použitím.