Warning! This project is not maintained anymore. If you already are a user, or consider becoming one, you should either step up as a maintainer or migrate to an alternative.
PL/scheme is a PostgreSQL procedural language handler for Scheme programming language released under BSD license. PL/scheme uses Guile in the background as its Scheme interpreter. With lots of builtin SRFIs, GOOPS framework and complete R5RS compliancy of Guile, PL/scheme will power up PostgreSQL procedures in your favorite programming language!
Input | Output | ||||||||||||||||
Simple "Hello, World!"
procedure.
| |||||||||||||||||
CREATE FUNCTION scm_pow(u int, v int) RETURNS int AS ' (let loop ((v v)) (if (< v 1) 1 (* u (loop (- v 1))))) ' LANGUAGE plscheme; | test=# SELECT * FROM scm_pow(2, 5); scm_pow --------- 32 | ||||||||||||||||
Debugging & backtracing example. | |||||||||||||||||
CREATE FUNCTION scm_backtrace_test(id int, name text) RETURNS text AS ' (let* ((a (lambda (u v) (format "ID: ~s, User Name: ~s" u v))) (b (a id name))) b) ' LANGUAGE plscheme; | test=# SELECT * FROM scm_backtrace_test(13, 'Volkan'); ERROR: Uncaugth exception thrown from PL/scheme procedure. DETAIL: Guile error output: Backtrace: In current input: 2: 0* (let* ((a (lambda # #)) (b (a id name))) b) 4: 1* [# | ||||||||||||||||
CREATE OR REPLACE FUNCTION scm_backtrace_test (id int, name text) RETURNS text AS ' (let* ((a (lambda (u v) (format #f "ID: ~s, User Name: ~s" u v))) (b (a id name))) b) ' LANGUAGE plscheme; | test=# SELECT * FROM scm_backtrace_test(13, 'Volkan'); scm_backtrace_test ----------------------------- ID: 13, User Name: "Volkan" (1 row) | ||||||||||||||||
Domain, row type argument and return value
example.
| |||||||||||||||||
CREATE DOMAIN mydom AS int CHECK (VALUE > 5); CREATE FUNCTION row_with_dom(IN rec record, OUT first int, OUT second mydom) AS $$ (let ((first (cdar rec)) (second (cdadr rec))) (list (cons "first" first) (cons "second" second))) $$ LANGUAGE plscheme; | test=# CREATE TABLE row_test (u int, v int); CREATE TABLE test=# INSERT INTO row_test (u, v) VALUES (1, 2); INSERT 0 1 test=# INSERT INTO row_test (u, v) VALUES (3, 4); INSERT 0 1 -- -- Let's violate our domain rule tied to "OUT second mydom". -- test=# SELECT row_with_dom(row_test) FROM row_test; ERROR: value for domain mydom violates check constraint "mydom_check" -- -- Trying again with some valid values. -- test=# UPDATE row_test SET u = (u * 10), v = (v * 20); UPDATE 2 test=# SELECT row_with_dom(row_test) FROM row_test; row_with_dom -------------- (40,10) (80,30) (2 rows) | ||||||||||||||||
CREATE OR REPLACE FUNCTION nested_rec(INOUT rec record, OUT len int) AS $$ (let ((f1 (assoc-ref rec "f1")) (f2 (assoc-ref rec "f2"))) (list (cons "rec" (list (cons "f1" f1) (cons "f2" f2) (cons "f1,f2" (list (cons "f1-f2" (- f1 f2)) (cons "f1*f2" (* f1 f2)))))) (cons "len" (length rec)))) $$ LANGUAGE plscheme; | -- -- You wanted to see some nested records, right? -- test=# SELECT * FROM nested_rec(ROW(1, 2)); rec | len ----------------+----- (1,2,"(-1,2)") | 2 (1 row) | ||||||||||||||||
SPI functionality.
| |||||||||||||||||
CREATE OR REPLACE FUNCTION spi_execute_test() RETURNS text AS $$ (let* ((ret (spi-execute (string-append "UPDATE t " " SET v = (v / 10) " " WHERE u % 2 = 0 " " RETURNING u, v"))) (status (assoc-ref ret "status")) (tuples (assoc-ref ret "returned-tuples"))) (letrec ((hash (lambda (lst val) ; Produce a signature by summing each ; tuple's u*v value. (if (null? lst) val ; Tuples are made of vectors. We'll ; just simply reach elements of the ; current tuple through vector ; accessor functions. (hash (cdr lst) (+ val (* (vector-ref (car lst) 0) (vector-ref (car lst) 1)))))))) (simple-format #f "Returned query status:\n~s\nSignature: ~s" status (hash tuples 0)))) $$ LANGUAGE plscheme; | test=# BEGIN; BEGIN -- -- Sandbox table. -- test=# CREATE TEMP TABLE t (u, v) AS test-# SELECT S.i, (S.i * 10) test-# FROM generate_series(1, 5) AS S (i); SELECT -- -- Now calling our test function. -- test=# SELECT spi_execute_test(); spi_test ------------------------------------------------------- Returned query status: #<uninterned-symbol spi-ok-update-returning b5e9a620> Signature: 20 (1 row) test=# ROLLBACK; ROLLBACK | ||||||||||||||||
CREATE OR REPLACE FUNCTION spi_prepare_test() RETURNS text AS $$ (let* ((plan (spi-prepare "SELECT $1, $2" (vector "text" "int4"))) (ret (spi-execute-prepared plan (vector "example text" "41")))) (simple-format #f (string-append "Returned query status:\n~s\n" "Returned tuples:\n~s") (assoc-ref ret "status") (assoc-ref ret "returned-tuples"))) $$ LANGUAGE plscheme; | test=# SELECT spi_prepare_test(); spi_prepare_test --------------------------------------------- Returned query status: #<uninterned-symbol spi-ok-select b5e44f90> Returned tuples: (#("example text" 41)) (1 row) | ||||||||||||||||
Trigger support.
| |||||||||||||||||
CREATE OR REPLACE FUNCTION valid_uv() RETURNS trigger AS $$ (let ((u (assoc-ref tg-tuple-new "u")) (v (assoc-ref tg-tuple-new "v"))) (cond ((or (> u 100) (< u 0)) ; Skip INSERT/UPDATE command '()) ((= v 0) ; Return (u, random(0, 100)) (list (cons "u" u) (cons "v" (random 100)))) ; Return original NEW tuple. (else tg-tuple-new))) $$ LANGUAGE plscheme; | test=# CREATE TABLE t (u int, v int); CREATE test=# CREATE TRIGGER t_valid_uv_trig test-# BEFORE INSERT OR UPDATE ON t test-# FOR EACH ROW EXECUTE PROCEDURE valid_uv(); CREATE TRIGGER -- Try to insert an invalid record. test=# INSERT INTO t VALUES (101, 101); INSERT 0 0 -- Insert a tuple whose v = 0, that will fire trigger -- to generate a random number for the v field. test=# INSERT INTO t VALUES (80, 0) RETURNING (u, v); row --------- (80,61) (1 row) -- Insert a valid record. test=# INSERT INTO t VALUES (99, 101); INSERT 0 1 | ||||||||||||||||
Exception handling support.
| |||||||||||||||||
CREATE OR REPLACE FUNCTION catch_test() RETURNS text AS $$ ; Define will be executed query. (define query (lambda () (symbol->string (assoc-ref (spi-execute "INSERT INTO foo VALUES (1);") "status")))) ; Define exception handler (define handler (lambda (key args) (string-append "Exception: " (symbol->string key) "\n" "Message: " args))) (catch 'undefined-table query handler) $$ LANGUAGE plscheme; | test=# SELECT catch_test(); catch_test ---------------------------------------- Exception: undefined-table Message: relation "foo" does not exist (1 row) | ||||||||||||||||
Error/Exception report
functionality.
| |||||||||||||||||
CREATE FUNCTION report_exception() RETURNS boolean AS ' (begin (report exception-level "Except me.") #t) ' LANGUAGE plscheme; CREATE FUNCTION report_notice() RETURNS boolean AS ' (begin (report notice-level "Notice me.") #t) ' LANGUAGE plscheme; | test=# SELECT report_exception(); ERROR: Except me. test=# SELECT report_notice(); NOTICE: Notice me. report_notice --------------- t (1 row) | ||||||||||||||||
SRF support.
| |||||||||||||||||
CREATE OR REPLACE FUNCTION setof_example_1(OUT foo int) RETURNS SETOF int AS $$ (list '(("foo" . 13)) '(("foo" . 14))) $$ LANGUAGE plscheme; | test=# SELECT * FROM setof_example_1(); foo ----- 13 14 (2 rows) | ||||||||||||||||
CREATE OR REPLACE FUNCTION setof_example_2(OUT foo int, OUT bar int) RETURNS SETOF record AS $$ (list '(("foo" . 1) ("bar" . 2)) '(("foo" . 3) ("bar" . 4))) $$ LANGUAGE plscheme; | test=# SELECT * FROM setof_example_2(); foo | bar -----+----- 1 | 2 3 | 4 (2 rows) | ||||||||||||||||
Globally shared variables.
CREATE FUNCTION set_var(num int) RETURNS int AS ' (let ((oldnum pl-shared)) (set! pl-shared num) oldnum) ' LANGUAGE plscheme; CREATE FUNCTION get_var() RETURNS int AS 'pl-shared' LANGUAGE plscheme; test=# SELECT set_var(13); set_var --------- 17 (1 row) test=# SELECT get_var(); get_var --------- 13 (1 row) Extensible type support. | -- -- Now I'll try to demonstrate an example for registering -- `point' SQL data type into data conversion routines. -- -- -- Modify dataconv.scm as described below: -- (define pl-data-imposers (list ; ; Point ; (list '("point") (lambda (value) (let ((token (string-tokenize value char-set:digit))) (cons (string->number (car token)) (string->number (cadr token)))))) ...)) ... (define pl-data-exposers (list ; ; Point ; (list (lambda (obj) (and (pair? obj) (number? (car obj)) (number? (cdr obj)))) (lambda (obj) (string-append (number->string (car obj)) "," (number->string (cdr obj)))) (lambda (obj) "point")) ...)) -- -- Here goes a procedure with argument of type point. -- CREATE OR REPLACE FUNCTION y_symmetry(p point) RETURNS point AS ' ; Calculate symmetric of the point through y-axis. (cons (* (car p) -1) (cdr p)) ' LANGUAGE plscheme IMMUTABLE; -- -- Dance like the wind bo! -- test=# SELECT y_symmetry('123,456'::point); y_symmetry ------------ (-123,456) (1 row) 1D array data type support. | -- -- Multiple every array item with 2. -- CREATE OR REPLACE FUNCTION arr_test_1(u int[]) RETURNS int[] AS $$ (list->vector (map (lambda (n) (* 2 n)) (vector->list u))) $$ LANGUAGE plscheme; test=# SELECT arr_test_1(ARRAY[1,2,3]); arr_test_1 ------------ {2,4,6} (1 row) -- -- Return an array with a NULL item. -- CREATE OR REPLACE FUNCTION arr_test_2() RETURNS int[] AS $$ (vector 1 2 '() 3) $$ LANGUAGE plscheme; test=# SELECT arr_test_2(); arr_test_2 -------------- {1,2,NULL,3} (1 row) CREATE OR REPLACE FUNCTION arr_test_3(u int[]) RETURNS text AS $$ (object->string u) $$ LANGUAGE plscheme; -- -- Pass an array with some NULL values. -- test=# SELECT arr_test_3(ARRAY[1,2,NULL,3]); arr_test_3 ------------- #(1 2 () 3) (1 row) Initialization script functionality. | |
You can fetch the sources from PL/scheme GitHub project page. You'll also need Guile libraries (version >= 1.8.0) to be able to compile and run PL/scheme. But luckily, most of the Linux/BSD distributions provide Guile by default, or at least there should be a binary package of it appropriate to your distribution.
The most easy way to compile and install PL/scheme is to use install.sh script that comes with the package:
$ ./install.sh --help Usage: ./install.sh [<options>] Available Options: --pg-config <path> pg_config executable's path. --module-dir <path> Scheme modules directory. (Default: pg_config --pkglibdir) --max-cache-size <n> Maximum number of (non-volatile and non-SRF) procedures to cache. --dbname <dbname> Database that will be connected to install PL/scheme into pg_pltemplate system catalog. (`postgres' will be used by default.) --safe-r5rs Compile PL/scheme as a trusted PL by excluding untrusted functions. (Barebone safe-r5rs module will be used.) After successfully finishing the installation, you may as well use plscheme[u].module_dir plscheme[u].cache_max_size GUC variables to alter the associated values on-the-fly. (Otherwise, values passed to PL/scheme at the compile time will be used as default.) $ CPPFLAGS="-I/opt/include" \ > LDFLAGS="-L/opt/lib -lpthread" \ > ./install.sh --pg-config /farm/usr/bin/pg_config pg_config : /farm/usr/bin/pg_config module-dir : /farm/usr/lib/postgresql max-cache-size: 64 dbname : postgres safe-r5rs : NO PSQL : /farm/usr/bin/psql postgres CPPFLAGS : -g -Wall -fpic -c -I/farm/usr/include/postgresql/server -I/opt/include LDFLAGS : -shared -lguile -L/opt/lib -lpthread Compiling... done. Linking... done. Copying files... plschemeu.so -> /farm/usr/lib/postgresql init.scm -> /farm/usr/lib/postgresql dataconv.scm -> /farm/usr/lib/postgresql Installing language into pg_pltemplate... INSERT 0 1 -------------------------------------------------------------------------- PL/scheme is succesfully compiled and introduced into pg_pltemplate system catalog. You can start using PL/scheme in any database you wish just after issuing "CREATE LANGUAGE plschemeu" command while connected to the related database. For more information about PL/scheme project you can visit our homepage at https://volkan.yazi.ci/plscheme
It's also possible to follow below steps that goes into depths of the compilation and installation process.
# # While compiling from sources, pay attention to Guile library directories and # PostgreSQL directories exposed by pg_config. Also, you need to define # MODULEDIR to make PL/scheme locate place its Scheme files. # PG_INCLUDEDIR=`pg_config --includedir-server` PG_PKGLIBDIR=`pg_config --pkglibdir` cc -g -Wall -fpic -c \ -DMODULE_DIR=\"$PG_PKGLIBDIR\" \ -DMAX_CACHE_SIZE=64 \ -I$PG_INCLUDEDIR \ -o plschemeu.o plscheme.c # You can also use a -DSAFE_R5RS flag to get a trusted PL. cc -lguile -shared -o plschemeu.so plschemeu.o cp plschemeu.so $PG_PKGLIBDIR cp init.scm $PG_PKGLIBDIR cp dataconv.scm $PG_PKGLIBDIR
After you've got a .so file by manually compiled from source or downloading a binary file, you can easily install PL/scheme into a specific database via below SQL command:
CREATE FUNCTION plschemeu_call_handler() RETURNS language_handler AS '$libdir/plschemeu' LANGUAGE C; CREATE LANGUAGE plschemeu HANDLER plschemeu_call_handler;
If everything went ok and you still get these kind of error message while trying to load plschemeu.so:
FATAL: could not load library "/../plschemeu.so": /.../libguile.so.17: undefined symbol: pthread_create
Just use -lpthread flag while linking plschemeu.so. (Or prepend -lpthread into your LDFLAGS environment varible just before calling ./install.sh script.)
Thanks so much to Rob Browning, Ludovic Courtès, David Fetter, Yeb Havinga, and Dale P. Smith for their kindly help.