Hlavní navigace

Rozšiřování PostgreSQL v C - Rozhraní SPI

Pavel Stěhule

V předchozích dílech jsem navrhovali funkce, jejichž výsledek závisel pouze na jejich parametrech a které z celého systému PostgreSQL používaly zatím jen datové struktury. Rozhraní SPI (Server Programming Interface), které máme k dispozici, nám však dovoluje mnohem více.

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ý“pro­blé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

Našli jste v článku chybu?

25. 12. 2002 14:20

Pavel Stěhule (neregistrovaný)

Vlastně vůbec netuším, jestli je podobná bloková alokace nutná a jestli se vyplatí. Patrně se vyplatí, protože se vyhýbám reallokaci paměti při každém přidání řetězce. Nakolik, nevím. Na druhou stranu zaberu víc paměti než je nutné.

Rozhodně jsem neobcházel mm PG. Prostě jen takhle řeším spojování většího počtu kratších řetězců, aniž bych použil vázaný seznam. Stejně bych to dělal v "klasickém" prostředí.




23. 12. 2002 11:58

Karel Zak (neregistrovaný)

"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."

Presne to same dela memory managment PG. Znamena to, ze delate blok v bloku a managment nad managmentem. Podle mne tim neziskate velkou vykonstni vyhodu (pokud ano tak je mmgr v PG spatny -- cemuz neverim:-)



Měšec.cz: Air Bank zruší TOP3 garanci a zdražuje kurzy

Air Bank zruší TOP3 garanci a zdražuje kurzy

Vitalia.cz: Jmenuje se Janina a žije bez cukru

Jmenuje se Janina a žije bez cukru

Vitalia.cz: Taky věříte na pravidlo 5 sekund?

Taky věříte na pravidlo 5 sekund?

Lupa.cz: Kdo pochopí vtip, může jít do ČT vyvíjet weby

Kdo pochopí vtip, může jít do ČT vyvíjet weby

Podnikatel.cz: Změny v cestovních náhradách 2017

Změny v cestovních náhradách 2017

Měšec.cz: U levneELEKTRO.cz už reklamaci nevyřídíte

U levneELEKTRO.cz už reklamaci nevyřídíte

Vitalia.cz: Potvrzeno: Pobyt v lese je skvělý na imunitu

Potvrzeno: Pobyt v lese je skvělý na imunitu

Podnikatel.cz: Chaos u EET pokračuje. Jsou tu další návrhy

Chaos u EET pokračuje. Jsou tu další návrhy

120na80.cz: Pánové, pečujte o svoje přirození a prostatu

Pánové, pečujte o svoje přirození a prostatu

Měšec.cz: Zdravotní a sociální pojištění 2017: Připlatíte

Zdravotní a sociální pojištění 2017: Připlatíte

Lupa.cz: Propustili je z Avastu, už po nich sahá ESET

Propustili je z Avastu, už po nich sahá ESET

Podnikatel.cz: Chtějte údaje k dani z nemovitostí do mailu

Chtějte údaje k dani z nemovitostí do mailu

Vitalia.cz: Proč vás každý zubař posílá na dentální hygienu

Proč vás každý zubař posílá na dentální hygienu

Podnikatel.cz: Udávání kvůli EET začalo

Udávání kvůli EET začalo

Měšec.cz: Jak vymáhat výživné zadarmo?

Jak vymáhat výživné zadarmo?

Vitalia.cz: Pamlsková vyhláška bude platit jen na základkách

Pamlsková vyhláška bude platit jen na základkách

Vitalia.cz: Jsou čajové sáčky toxické?

Jsou čajové sáčky toxické?

DigiZone.cz: Rádio Šlágr má licenci pro digi vysílání

Rádio Šlágr má licenci pro digi vysílání

Vitalia.cz: Když přijdete o oko, přijdete na rok o řidičák

Když přijdete o oko, přijdete na rok o řidičák

120na80.cz: Co všechno ovlivňuje ženskou plodnost?

Co všechno ovlivňuje ženskou plodnost?