pgplsh, czyli czego to ludzie nie wymyślą

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