Podkusiło mnie coś, żeby zajrzeć dziś w appendiksy
w podręczniku do PostgreSQL'a. Podkusiło i pokarało.
Wyczytałem tam, że jest takie coś, jak pgplsh - shellowy język proceduralny. Długo nie myślałem, ściągnąłem źródła (wersja 1.2) i zabrałem się za instalację.
kooph@turek ~/Desktop/pgplsh-1.2 $ env CPPFLAGS="-I/usr/local/include" ./configure --prefix=/usr/local/ && make
Poszło nadzwyczaj gładko, więc wietrzyłem tu jakiś podstęp. Swędzenie w skarpetce mnie nie myliło - jak tylko chciałem zainstalować język (biblioteki i skrypty trafiły do ${prefix}/lib/pgplsh i do ${prefix}/share/pgplsh), to PostgreSQL napisał mi.. no, brzydko mi napisał:
kooph@turek /usr/local/share/pgplsh $ psql test < createlang_pgplsh.sql
ERROR: incompatible library "/usr/local//lib/pgplsh/pgplsh.so": missing magic block
HINT: Extension libraries are required to use the PG_MODULE_MAGIC macro.
ERROR: incompatible library "/usr/local//lib/pgplsh/pgplsh.so": missing magic block
HINT: Extension libraries are required to use the PG_MODULE_MAGIC macro.
ERROR: function pg_catalog.plsh_handler() does not exist
Tu się podrapałem po głowie i zajrzałem do, jakże nieocenionego w takich sytuacjach, manuala. Tja.. Autor napisał na stronie projektu, że język był testowany z PostgreSQLem 7.4 i 8.1. Z podręcznika wynika, że od wersji 8.2 wszystkie moduły powinny być budowane z makrem PG_MODULE_MAGIC. Tak więc majstrowanie przy źródłach..
Dodałem trzy linijki po inkludzie fmgr.h, jak radziła mądra księga:
#include
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
i przekonfigurowałem z uwzględnieniem makra:
kooph@turek ~/Desktop/pgplsh-1.2 $ env CPPFLAGS="-I/usr/local/include" CFLAGS="-DPG_MODULE_MAGIC" ./configure --prefix=/usr/local/
Kompilacja przebiegła gładko, instalacja także. Można się zacząć bawić, ale zanim co, to kilka uwag:
- funkcje zaczynamy od magicznej linijki #!/bin/sh. Jeśli komuś sh/bash nie pasuje, to może użyć innego shella (a co!)
- parametry są przekazywane pozycyjnie
- wszystko, co normalnie zostanie wydrukowane na standardowym wyjściu zostanie przekazane jako kolumna z outputem, więc wynik funkcji musi być typem tekstowym
- wszystko, co zostanie normalnie wyplute na stderr, spowoduje wyjątek
- również niezerowe wyjście ze skryptu spowoduje wyjątek
- można użyć plsh jako języka do triggerów, ale nie da się modyfikować wierszy, a wszystkie printy w triggerze przepadną..
- ponieważ język jest w stanie operować poza środowiskiem bazy, jest untrusted
Zróbmy sobie funkcję:
CREATE OR REPLACE FUNCTION test_sh() RETURNS varchar AS
$BODY$#!/bin/bash
for e in `env`
do
echo $e;
done;
$BODY$
LANGUAGE 'plsh' VOLATILE;
a teraz:
test=# select * from test_sh();
test_sh
----------------------------------------------------------------------------------
LC_MONETARY=C
SHELL=/bin/bash
TERM=xterm
DEFAULTLEVEL=default
LC_NUMERIC=C
USER=root
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/sbin:/sbin:/bin:/usr/sbin:/usr/bin
LC_MESSAGES=pl_PL
_=/bin/env
LC_COLLATE=pl_PL
PWD=/var/lib/pgsql
LANG=pl_PL
PGSYSCONFDIR=/usr/local/etc/postgresql
BOOTLEVEL=boot
CONSOLETYPE=pty
SVCNAME=postgresql
HOME=/root
SHLVL=2
LC_CTYPE=pl_PL
SOFTLEVEL=default
LC_TIME=C
(1 row)
test=#
No to jeszcze raz:
CREATE FUNCTION test_sh(liczba int4, data timestamptz)
RETURNS text AS
$BODY$#!/bin/bash
for e
do
echo $e
done
$BODY$
LANGUAGE 'plsh' VOLATILE;
test=# select * from test_sh(1, current_timestamp);
test_sh
-------------------------------
1
2007-05-28 22:15:43.018073+02
(1 row)
test=#
proste? proste. przydatne? hmm.. to zależy:)
Patrys zwrócił uwagę na sposób wykonywania funkcji. Wiadomo - wykonywane jest w subshellu. Ale, żeby to zrobić, to postgres tworzy plik tymczasowy (mkstemp) w /tmp ('mało bezpieczne'). Aby obejść ograniczenia noexec często zakładane na ten mountpoint, z ciała funkcji najpierw wyciągany jest wiersz ze ścieżką do shella (shebang), a do pliku jest wrzucana zawartość bez tego wiersza. Procedura wykonywana jest jako '/sciezka/do/shella /tmp/plik-tymczasowy parametry'.
