Pokud si vzpomínáte na předchozí seriál Úvod do PL/pgSQL, pak si jistě vybavíte příklad agregační funkce html_list, která vracela seznam položek v sloupci formátovaný jako html seznam. Totožnou fonkci bude mít funkce html_list. Jejími parametry bude SQL dotaz, sloupec, jehož položky chceme uložit do seznamu, maximální počet zpracovávaných řádek a velikost bloku alokované paměti.
Pro náš příklad si vystačíme s pěti základními funkcemi: SPI_connect, SPI_finish, SPI_exec, SPI_fnumber a SPI_getvalue. S touto množinou funkcí dokážeme pouze číst výsledek dotazu, nicméně je to v tuto chvíli dostačující. Funkce SPI_connect inicializuje rozhraní a musí proto být volána jako první. Naproti tomu SPI_finish uvolňuje veškerou paměť alokovanou funkcí palloc po dobu běhu funkce a musí být proto volána jako poslední. Pokud naše funkce vrací ukazatel na paměť alokovanou v proceduře (u všech hodnot předávaných pdkazem), nesmí se paměť alokovat příkazem palloc. To z toho důvodu, že ještě než stihneme hodnotu předat volající funkci, SPI_finish stihne pamět s touto hodnotou uvolnit. Tento „neřešitelný“problém řeší alternativní procedury pro práci s pamětí: SPI_palloc a SPI_replloc. Volání SPI_finish nemá vliv na paměť alokovanou těmito funkcemi. Pokud dojde k ukončení funkce voláním elog (ERROR, …), potom je SPI_finish voláno automaticky.
Pokud dojde k chybě, pak SPI funkce vrací obvykle záporné číslo. Po aktivaci rozhraní můžeme funkcí SPI_exec nechat provést SQL příkaz. Druhým parametrem funkce SPI_exec je maximální počet vrácených záznamů. Pokud je nula, pak se použijí všechny záznamy. Po bezchybném provedení dotazu můžeme používat globální proměnnou SPI_processed obsahující počet vrácených záznamů a globální ukazatel SPI_typtable.
Ukazatel SPI_tuptable je ukazatel na strukturu obsahující jednak pole s vrácenými řádkami, jednak odkaz na popis struktury vrácených dat. Pro získání hodnoty ve sloupečku potřebujeme vědět pořadí sloupce v dotazu. Pořadí vrací funkce SPI_fnumber, např.
sloupec = SPI_fnumber(SPI_tuptable->tupdesc, "jmeno"); if (sloupec <= 0) elog (ERROR, "SPI_ERROR_NOATTRIBUTE");
Přístup k n-tému sloupečku x-tého řádku zajišťuje funkce SPI_getvalue. Je nutné si uvědomovat, že všechny hodnoty vrácené SPI_getvalue jsou textové. Získání první hodnoty prvního řádku provedeme příkazem:
printf ("%s", SPI_getvalue(
SPI_tuptable->vals[0],
SPI_tuptable->tupdesc, 1));
Abych urychlil provádění funkce, nealokuji paměť při každém přidání řetězce, ale po blocích – stránkách, jejichž velikost si sami určíme.
text *sts_strcatex2 (char *str, text *txt,
long *zapsano, long *zbyva, long stranka)
{
long len = strlen (str);
if (len > stranka)
elog (ERROR, "Retezec je vetsi nez velikost stranky");
if (*zbyva < len)
{
long nsize = (((*zapsano + len) / stranka) + 1) * stranka;
txt = (text *) SPI_repalloc (txt, nsize + VARHDRSZ);
*zbyva = nsize - *zapsano;
}
memcpy ((char*)VARDATA (txt) + *zapsano, str, len);
*zapsano += len; VARATT_SIZEP (txt) = *zapsano + VARHDRSZ;
*zbyva -= len;
return (txt);
}
Paměť alokuji voláním funkce SPI_repalloc a nikoliv repalloc z již zmíněného důvodu – pokud bych použil palloc, pak fce SPI_finish by tuto paměť uvolnila. Následné volání PG_RETURN_TEXT_P by vracelo neplatný pointer, což vede (jak jsem se sám přesvědčil zhruba v 80%) k pádu backendu.
Základ funkce html_list je následující:
SPI_connect ();
if ((ret = SPI_exec (query, max)) < 0)
elog (ERROR, "SPI_exec vrátilo %d", ret);
sloupec = SPI_fnumber(SPI_tuptable->tupdesc, jm_sloupce);
if (sloupec <= 0)
elog (ERROR, "SPI_ERROR_NOATTRIBUTE");
zapsano = 0; zbyva = stranka;
txt = (text*) SPI_palloc (VARHDRSZ + zbyva);
txt = sts_strcatex2 ("<ul>\n", txt, &zapsano, &zbyva, stranka);
for (radek = 0; radek < SPI_processed; radek++)
{
txt = sts_strcatex2 ("<li>", txt, &zapsano, &zbyva, stranka);
txt = sts_strcatex2 (SPI_getvalue (SPI_tuptable->vals[radek],
SPI_tuptable->tupdesc, sloupec),
txt, &zapsano, &zbyva, stranka);
txt = sts_strcatex2 ("</li>\n", txt, &zapsano,
&zbyva, stranka);
}
txt = sts_strcatex2 ("</ul>", txt, &zapsano, &zbyva, stranka);
SPI_finish ();
PG_RETURN_TEXT_P(txt);
K textu není co dodat. V cyklu čtu jednotlivé hodnoty sloupce (SPI_getvalue vrací vždy ukazatel na řetězec), které přidávám do struktury txt typu text.
Po překladu můžeme funkci zaregistrovat v PostgreSQL
CREATE OR REPLACE FUNCTION html_list (cstring, cstring, int, int) RETURNS text AS 'html_list.so','html_list' LANGUAGE 'C';
Tabulka, kterou jsem pro test použil, obsahuje 192 záznamů, výsledkem je text o délce mírně přesahující 6KB. Pro změření casu jsem napsal krátký skript v Pythonu. Jelikož jsem chtel použít profiler Pythonu, rozdělil jsem příkazy do samostatných funkcí.
#!/usr/local/bin/python
import psycopg, time
def plain(cur):
cur.execute ("SELECT jmeno from jmena")
def html_list(cur):
cur.execute ("SELECT html_list ('select jmeno from jmena',
'jmeno',0,8000)")
rows = cur.fetchall ()
print rows[0][0]
def pyt(cur):
cur.execute ("SELECT jmeno from jmena")
rows = cur.fetchall ()
print "<ul>"
for r in rows:
print "<li>%s</li>" % r[0]
print "</ul>"
def main ():
for i in range(100):
con = psycopg.connect ("dbname=testdb011")
cur = con.cursor ()
plain(cur)
html_list(cur)
pyt(cur)
import profile
profile.run ('main()')
Výsledkem je tato tabulka
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 4.670 4.670 <string>:1(?)
1 0.090 0.090 4.760 4.760 profile:0(main())
0 0.000 0.000 profile:0(profiler)
100 3.670 0.037 3.670 0.037 test.py:12(pyt)
1 0.620 0.620 4.670 4.670 test.py:20(main)
100 0.250 0.003 0.250 0.003 test.py:4(plain)
100 0.130 0.001 0.130 0.001 test.py:7(html_list)
Z ní je zřejmé, že vytvoření seznamu funkcí html_list je oproti „normálnímu“ sestavení seznamu zhruba 28krát rychlejší. Důvodem jsou nejspíš pomalejší operace Pythonu s řetězci, ale i větší režie spojená s procházením pole v Pythonu.
Celý příklad naleznete v archivu.
Příští díl bude poslední a bude věnován tabulkovým funkcím PostgreSQL.
Zdroje
- Dokumentace
- Ostatní