Add plpython code.

This commit is contained in:
Bruce Momjian 2001-05-09 19:54:38 +00:00
parent 6319f5da37
commit 0bef7ba549
26 changed files with 3891 additions and 1 deletions

View File

@ -4,7 +4,7 @@
#
# Copyright (c) 1994, Regents of the University of California
#
# $Header: /cvsroot/pgsql/src/pl/Makefile,v 1.17 2000/10/24 19:31:13 tgl Exp $
# $Header: /cvsroot/pgsql/src/pl/Makefile,v 1.18 2001/05/09 19:54:38 momjian Exp $
#
#-------------------------------------------------------------------------
@ -22,6 +22,10 @@ ifeq ($(with_perl), yes)
DIRS += plperl
endif
ifeq ($(with_python), yes)
DIRS += plpython
endif
all install installdirs uninstall depend distprep:
@for dir in $(DIRS); do $(MAKE) -C $$dir $@ || exit; done

69
src/pl/plpython/Makefile Normal file
View File

@ -0,0 +1,69 @@
# cflags. pick your favorite
#
CC=gcc
CFLAGS=-g -O0 -Wall -Wmissing-declarations -fPIC
# build info for python, alter as needed
#
# python headers
#
#INCPYTHON=/usr/include/python1.5
INCPYTHON=/usr/include/python2.0
# python shared library
#
#LIBPYTHON=python1.5
LIBPYTHON=python2.0
# if python is someplace odd
#
LIBPYTHONPATH=/usr/lib
# python 2 seems to want libdb
# various db libs are still messed on my system
#
#LIBPYTHONEXTRA=/usr/lib/libdb2.so.2.7.7
#LIBPYTHONEXTRA=-ldb2
LDPYTHON=-L$(LIBPYTHONPATH) -l$(LIBPYTHON) $(LIBPYTHONEXTRA)
# build info for postgres
#
# postgres headers. the installed include directory doesn't work for me
#
#INCPOSTGRES=/usr/include/postgres
INCPOSTGRES=/home/andrew/builds/postgresql/src/include
# hopefully you won't need this utter crap...
# but if you can't patch the appropriate dynloader file, try this. you
# may have to add other modules.
#
#DLDIR=/usr/lib/python1.5/lib-dynload
#DLHACK=$(DLDIR)/arraymodule.so $(DLDIR)/timemodule.so $(DLDIR)/cmathmodule.so $(DLDIR)/errnomodule.so $(DLDIR)/mathmodule.so $(DLDIR)/md5module.so $(DLDIR)/operator.so
# $(DLDIR)/shamodule.so
# shouldn't need to alter anything below here
#
INCLUDES=-I$(INCPYTHON) -I$(INCPOSTGRES) -I./
# dynamic linker flags.
#
#LDFLAGS=--shared -Wl,-Bshareable -Wl,-E -Wl,-soname,$@
LDFLAGS=--shared -Wl,-E -Wl,-soname,$@
.PHONY: clean
all: plpython.so
plpython.o: plpython.c plpython.h
$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<
plpython.so: plpython.o
$(CC) $(LDFLAGS) -o $@ $^ $(LDPYTHON) $(DLHACK) -ldl -lpthread -lm
clean:
rm -f plpython.so *.o

171
src/pl/plpython/README Normal file
View File

@ -0,0 +1,171 @@
*** INSTALLING ***
0) Build, install or borrow postgresql 7.1, not 7.0. I've got
a language module for 7.0, but it has no SPI interface. Build is best
because it will allow you to do
"cd postgres/src/"
"patch -p2 < dynloader.diff"
or if that fails open linux.h in src/backend/ports/dynloader and
change the pg_dlopen define from
#define pg_dlopen(f) dlopen(f, 2)
to
#define pg_dlopen(f) dlopen(f, (RTLD_NOW|RTLD_GLOBAL))
adding the RTLD_GLOBAL flag to the dlopen call allows libpython to
properly resolve symbols when it loads dynamic module. If you can't
patch and rebuild postgres read about DLHACK in the next section.
1) Edit the Makefile. Basically select python 2.0 or 1.5, and set
the include file locations for postgresql and python. If you can't
patch linux.h (or whatever file is appropriate for your architecture)
to add RTLD_GLOBAL to the pg_dlopen/dlopen function and rebuild
postgres. You must uncomment the DLHACK and DLDIR variables. You may
need to alter the DLDIR and add shared modules to DLHACK. This
explicitly links the shared modules to the plpython.so file, and
allows libpython find required symbols. However you will NOT be able
to import any C modules that are not explicitly linked to
plpython.so. Module dependencies get ugly, and all in all it's a
crude hack.
2) Run make.
3) Copy 'plpython.so' to '/usr/local/lib/postgresql/lang/'.
The scripts 'update.sh' and 'plpython_create.sql' are hard coded to
look for it there, if you want to install the module elsewhere edit
them.
4) Optionally type 'test.sh', this will create a new database
'pltest' and run some checks. (more checks needed)
5) 'psql -Upostgres yourTESTdb < plpython_create.sql'
*** USING ***
There are sample functions in 'plpython_function.sql'.
Remember that the python code you write gets transformed into a
function. ie.
CREATE FUNCTION myfunc(text) RETURNS text
AS
'return args[0]'
LANGUAGE 'plpython';
gets tranformed into
def __plpython_procedure_myfunc_23456():
return args[0]
where 23456 is the Oid of the function.
If you don't provide a return value, python returns the default 'None'
which probably isn't what you want. The language module transforms
python None to postgresql NULL.
Postgresql function variables are available in the global "args" list.
In the myfunc example, args[0] contains whatever was passed in as the
text argument. For myfunc2(text, int4), args[0] would contain the
text variable and args[1] the int4 variable. The global dictionary SD
is available to store data between function calls. This variable is
private static data. The global dictionary GD is public data,
available to all python functions within a backend. Use with care.
When the function is used in a trigger, the triggers tuples are in
TD["new"] and/or TD["old"] depending on the trigger event. Return
'None' or "OK" from the python function to indicate the tuple is
unmodified, "SKIP" to abort the event, or "MODIFIED" to indicate
you've modified the tuple. If the trigger was called with arguments
they are available in TD["args"][0] to TD["args"][(n -1)]
Each function gets it's own restricted execution object in the python
interpreter so global data, function arguments from myfunc are not
available to myfunc2. Except for data in the GD dictionary, as
mentioned above.
The plpython language module automatically imports a python module
called 'plpy'. The functions and constants in this module are
available to you in the python code as 'plpy.foo'. At present 'plpy'
implements the functions 'plpy.error("msg")', 'plpy.fatal("msg")',
'plpy.debug("msg")' and 'plpy.notice("msg")'. They are mostly
equivalent to calling 'elog(LEVEL, "msg")', where level is DEBUG,
ERROR, FATAL or NOTICE. 'plpy.error', and 'plpy.fatal' actually raise
a python exception which if uncaught causes the plpython module to
call elog(ERROR, msg) when the function handler returns from the
python interpreter. Long jumping out of the python interpreter
probably isn't good. 'raise plpy.ERROR("msg")' and 'raise
plpy.FATAL("msg") are equivalent to calling plpy.error or plpy.fatal.
Additionally the in the plpy module there are two functions called
execute and prepare. Calling plpy.execute with a query string, and
optional limit argument, causing that query to be run, and the result
returned in a result object. The result object emulates a list or
dictionary objects. The result object can be accessed by row number,
and field name. It has these additional methods: nrows() which
returns the number of rows returned by the query, and status which is
the SPI_exec return variable. The result object can be modified.
rv = plpy.execute("SELECT * FROM my_table", 5)
returns up to 5 rows from my_table. if my_table a column my_field it
would be accessed as
foo = rv[i]["my_field"]
The second function plpy.prepare is called with a query string, and a
list of argument types if you have bind variables in the query.
plan = plpy.prepare("SELECT last_name FROM my_users WHERE first_name =
$1", [ "text" ])
text is the type of the variable you will be passing as $1. After
preparing you use the function plpy.execute to run it.
rv = plpy.execute(plan, [ "name" ], 5)
The limit argument is optional in the call to plpy.execute.
When you prepare a plan using the plpython module it is automatically
saved. Read the SPI documentation for postgresql for a description of
what this means. Anyway the take home message is if you do:
plan = plpy.prepare("SOME QUERY")
plan = plpy.prepare("SOME OTHER QUERY")
You are leaking memory, as I know of no way to free a saved plan. The
alternative of using unsaved plans it even more painful (for me).
*** BUGS ***
If the module blows up postgresql or bites your dog, please send a
script that will recreate the behaviour. Back traces from core dumps
are good, but python reference counting bugs and postgresql exeception
handling bugs give uninformative back traces (you can't long_jmp into
functions that have already returned? *boggle*)
*** TODO ***
1) create a new restricted execution class that will allow me to pass
function arguments in as locals. passing them as globals means
function cannot be called recursively...
2) Functions cache the input and output functions for their arguments,
so the following will make postgres unhappy
create table users (first_name text, last_name text);
create function user_name(user) returns text as 'mycode' language 'plpython';
select user_name(user) from users;
alter table add column user_id int4;
select user_name(user) from users;
you have to drop and create the function(s) each time it's arguments
are modified (not nice), don't cache the input and output functions
(slower?), or check if the structure of the argument has been altered
(is this possible, easy, quick?) and recreate cache.
3) better documentation
4) suggestions?

View File

@ -0,0 +1,12 @@
--- postgresql-snapshot-12-13-2000/src/backend/port/dynloader/linux.h Mon May 29 03:00:17 2000
+++ postgresql-snapshot/src/backend/port/dynloader/linux.h Sun Feb 4 23:30:59 2001
@@ -32,7 +32,8 @@
#endif
#else
/* #define pg_dlopen(f) dlopen(f, 1) */
-#define pg_dlopen(f) dlopen(f, 2)
+/* #define pg_dlopen(f) dlopen(f, 2) */
+#define pg_dlopen(f) dlopen(f, (RTLD_NOW|RTLD_GLOBAL))
#define pg_dlsym dlsym
#define pg_dlclose dlclose
#define pg_dlerror dlerror

View File

@ -0,0 +1,18 @@
--- error.expected Sat Mar 31 16:15:31 2001
+++ error.output Thu Apr 19 23:47:53 2001
@@ -1,5 +1,5 @@
SELECT invalid_type_uncaught('rick');
-ERROR: plpython: Call of function `__plpython_procedure_invalid_type_uncaught_1175341' failed.
+ERROR: plpython: Call of function `__plpython_procedure_invalid_type_uncaught_1289666' failed.
plpy.SPIError: Cache lookup for type `test' failed.
SELECT invalid_type_caught('rick');
NOTICE: ("Cache lookup for type `test' failed.",)
@@ -9,7 +9,7 @@
(1 row)
SELECT invalid_type_reraised('rick');
-ERROR: plpython: Call of function `__plpython_procedure_invalid_type_reraised_1175343' failed.
+ERROR: plpython: Call of function `__plpython_procedure_invalid_type_reraised_1289668' failed.
plpy.Error: ("Cache lookup for type `test' failed.",)
SELECT valid_type('rick');
valid_type

View File

@ -0,0 +1,19 @@
SELECT invalid_type_uncaught('rick');
ERROR: plpython: Call of function `__plpython_procedure_invalid_type_uncaught_1175341' failed.
plpy.SPIError: Cache lookup for type `test' failed.
SELECT invalid_type_caught('rick');
NOTICE: ("Cache lookup for type `test' failed.",)
invalid_type_caught
---------------------
(1 row)
SELECT invalid_type_reraised('rick');
ERROR: plpython: Call of function `__plpython_procedure_invalid_type_reraised_1175343' failed.
plpy.Error: ("Cache lookup for type `test' failed.",)
SELECT valid_type('rick');
valid_type
------------
(1 row)

View File

@ -0,0 +1,19 @@
SELECT invalid_type_uncaught('rick');
ERROR: plpython: Call of function `__plpython_procedure_invalid_type_uncaught_1289666' failed.
plpy.SPIError: Cache lookup for type `test' failed.
SELECT invalid_type_caught('rick');
NOTICE: ("Cache lookup for type `test' failed.",)
invalid_type_caught
---------------------
(1 row)
SELECT invalid_type_reraised('rick');
ERROR: plpython: Call of function `__plpython_procedure_invalid_type_reraised_1289668' failed.
plpy.Error: ("Cache lookup for type `test' failed.",)
SELECT valid_type('rick');
valid_type
------------
(1 row)

View File

@ -0,0 +1,11 @@
--- feature.expected Sat Mar 31 16:15:31 2001
+++ feature.output Thu Apr 19 23:47:52 2001
@@ -29,7 +29,7 @@
(1 row)
SELECT import_fail();
-NOTICE: ('import socket failed -- untrusted dynamic module: socket',)
+NOTICE: ('import socket failed -- untrusted dynamic module: _socket',)
import_fail
--------------------
failed as expected

View File

@ -0,0 +1,139 @@
select stupid();
stupid
--------
zarkon
(1 row)
SELECT static_test();
static_test
-------------
1
(1 row)
SELECT static_test();
static_test
-------------
2
(1 row)
SELECT global_test_one();
global_test_one
--------------------------------------------------------
SD: set by global_test_one, GD: set by global_test_one
(1 row)
SELECT global_test_two();
global_test_two
--------------------------------------------------------
SD: set by global_test_two, GD: set by global_test_one
(1 row)
SELECT import_fail();
NOTICE: ('import socket failed -- untrusted dynamic module: socket',)
import_fail
--------------------
failed as expected
(1 row)
SELECT import_succeed();
import_succeed
------------------------
succeeded, as expected
(1 row)
SELECT import_test_one('sha hash of this string');
import_test_one
------------------------------------------
a04e23cb9b1a09cd1051a04a7c571aae0f90346c
(1 row)
select import_test_two(users) from users where fname = 'willem';
import_test_two
-------------------------------------------------------------------
sha hash of willemdoe is 3cde6b574953b0ca937b4d76ebc40d534d910759
(1 row)
select argument_test_one(users, fname, lname) from users where lname = 'doe';
argument_test_one
-------------------------------------------------------------------------------------
willem doe => {'fname': 'willem', 'userid': 3, 'lname': 'doe', 'username': 'w_doe'}
john doe => {'fname': 'john', 'userid': 2, 'lname': 'doe', 'username': 'johnd'}
jane doe => {'fname': 'jane', 'userid': 1, 'lname': 'doe', 'username': 'j_doe'}
(3 rows)
select nested_call_one('pass this along');
nested_call_one
-----------------------------------------------------------------
{'nested_call_two': "{'nested_call_three': 'pass this along'}"}
(1 row)
select spi_prepared_plan_test_one('doe');
spi_prepared_plan_test_one
----------------------------
there are 3 does
(1 row)
select spi_prepared_plan_test_one('smith');
spi_prepared_plan_test_one
----------------------------
there are 1 smiths
(1 row)
select spi_prepared_plan_test_nested('smith');
spi_prepared_plan_test_nested
-------------------------------
there are 1 smiths
(1 row)
SELECT * FROM users;
fname | lname | username | userid
--------+-------+----------+--------
jane | doe | j_doe | 1
john | doe | johnd | 2
willem | doe | w_doe | 3
rick | smith | slash | 4
(4 rows)
UPDATE users SET fname = 'william' WHERE fname = 'willem';
INSERT INTO users (fname, lname) VALUES ('william', 'smith');
INSERT INTO users (fname, lname, username) VALUES ('charles', 'darwin', 'beagle');
SELECT * FROM users;
fname | lname | username | userid
---------+--------+----------+--------
jane | doe | j_doe | 1
john | doe | johnd | 2
willem | doe | w_doe | 3
rick | smith | slash | 4
willem | smith | w_smith | 5
charles | darwin | beagle | 6
(6 rows)
SELECT join_sequences(sequences) FROM sequences;
join_sequences
----------------
ABCDEFGHIJKL
ABCDEF
ABCDEF
ABCDEF
ABCDEF
ABCDEF
(6 rows)
SELECT join_sequences(sequences) FROM sequences
WHERE join_sequences(sequences) ~* '^A';
join_sequences
----------------
ABCDEFGHIJKL
ABCDEF
ABCDEF
ABCDEF
ABCDEF
ABCDEF
(6 rows)
SELECT join_sequences(sequences) FROM sequences
WHERE join_sequences(sequences) ~* '^B';
join_sequences
----------------
(0 rows)

View File

@ -0,0 +1,139 @@
select stupid();
stupid
--------
zarkon
(1 row)
SELECT static_test();
static_test
-------------
1
(1 row)
SELECT static_test();
static_test
-------------
2
(1 row)
SELECT global_test_one();
global_test_one
--------------------------------------------------------
SD: set by global_test_one, GD: set by global_test_one
(1 row)
SELECT global_test_two();
global_test_two
--------------------------------------------------------
SD: set by global_test_two, GD: set by global_test_one
(1 row)
SELECT import_fail();
NOTICE: ('import socket failed -- untrusted dynamic module: _socket',)
import_fail
--------------------
failed as expected
(1 row)
SELECT import_succeed();
import_succeed
------------------------
succeeded, as expected
(1 row)
SELECT import_test_one('sha hash of this string');
import_test_one
------------------------------------------
a04e23cb9b1a09cd1051a04a7c571aae0f90346c
(1 row)
select import_test_two(users) from users where fname = 'willem';
import_test_two
-------------------------------------------------------------------
sha hash of willemdoe is 3cde6b574953b0ca937b4d76ebc40d534d910759
(1 row)
select argument_test_one(users, fname, lname) from users where lname = 'doe';
argument_test_one
-------------------------------------------------------------------------------------
willem doe => {'fname': 'willem', 'userid': 3, 'lname': 'doe', 'username': 'w_doe'}
john doe => {'fname': 'john', 'userid': 2, 'lname': 'doe', 'username': 'johnd'}
jane doe => {'fname': 'jane', 'userid': 1, 'lname': 'doe', 'username': 'j_doe'}
(3 rows)
select nested_call_one('pass this along');
nested_call_one
-----------------------------------------------------------------
{'nested_call_two': "{'nested_call_three': 'pass this along'}"}
(1 row)
select spi_prepared_plan_test_one('doe');
spi_prepared_plan_test_one
----------------------------
there are 3 does
(1 row)
select spi_prepared_plan_test_one('smith');
spi_prepared_plan_test_one
----------------------------
there are 1 smiths
(1 row)
select spi_prepared_plan_test_nested('smith');
spi_prepared_plan_test_nested
-------------------------------
there are 1 smiths
(1 row)
SELECT * FROM users;
fname | lname | username | userid
--------+-------+----------+--------
jane | doe | j_doe | 1
john | doe | johnd | 2
willem | doe | w_doe | 3
rick | smith | slash | 4
(4 rows)
UPDATE users SET fname = 'william' WHERE fname = 'willem';
INSERT INTO users (fname, lname) VALUES ('william', 'smith');
INSERT INTO users (fname, lname, username) VALUES ('charles', 'darwin', 'beagle');
SELECT * FROM users;
fname | lname | username | userid
---------+--------+----------+--------
jane | doe | j_doe | 1
john | doe | johnd | 2
willem | doe | w_doe | 3
rick | smith | slash | 4
willem | smith | w_smith | 5
charles | darwin | beagle | 6
(6 rows)
SELECT join_sequences(sequences) FROM sequences;
join_sequences
----------------
ABCDEFGHIJKL
ABCDEF
ABCDEF
ABCDEF
ABCDEF
ABCDEF
(6 rows)
SELECT join_sequences(sequences) FROM sequences
WHERE join_sequences(sequences) ~* '^A';
join_sequences
----------------
ABCDEFGHIJKL
ABCDEF
ABCDEF
ABCDEF
ABCDEF
ABCDEF
(6 rows)
SELECT join_sequences(sequences) FROM sequences
WHERE join_sequences(sequences) ~* '^B';
join_sequences
----------------
(0 rows)

44
src/pl/plpython/linux.h Normal file
View File

@ -0,0 +1,44 @@
/*-------------------------------------------------------------------------
*
* port_protos.h
* port-specific prototypes for Linux
*
*
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: linux.h,v 1.1 2001/05/09 19:54:38 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#ifndef PORT_PROTOS_H
#define PORT_PROTOS_H
#include "fmgr.h"
#include "utils/dynamic_loader.h"
#ifdef __ELF__
#include <dlfcn.h>
#endif
/* dynloader.c */
#ifndef __ELF__
#ifndef HAVE_DLD_H
#define pg_dlsym(handle, funcname) (NULL)
#define pg_dlclose(handle) ({})
#else
#define pg_dlsym(handle, funcname) ((PGFunction) dld_get_func((funcname)))
#define pg_dlclose(handle) ({ dld_unlink_by_file(handle, 1); free(handle); })
#endif
#else
/* #define pg_dlopen(f) dlopen(f, 1) */
/* #define pg_dlopen(f) dlopen(f, 2) */
#define pg_dlopen(f) dlopen(f, (RTLD_NOW|RTLD_GLOBAL))
#define pg_dlsym dlsym
#define pg_dlclose dlclose
#define pg_dlerror dlerror
#endif
/* port.c */
#endif /* PORT_PROTOS_H */

2623
src/pl/plpython/plpython.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,66 @@
#ifndef PLPYTHON_NEW_H
#define PLPYTHON_NEW_H
#define DEBUG_EXC 0
#define DEBUG_LEVEL 0
#define DECLARE_N_EXC(N) int rv_##N; sigjmp_buf buf_##N
#define TRAP_N_EXC(N) ((rv_##N = sigsetjmp(Warn_restart, 1)) != 0)
#if !DEBUG_EXC
# define RESTORE_N_EXC(N) memcpy(&Warn_restart, &(buf_##N), sizeof(sigjmp_buf))
# define SAVE_N_EXC(N) memcpy(&(buf_##N), &Warn_restart, sizeof(sigjmp_buf))
# define RERAISE_N_EXC(N) siglongjmp(Warn_restart, rv_##N)
# define RAISE_EXC(V) siglongjmp(Warn_restart, (V))
#else
# define RESTORE_N_EXC(N) do { \
elog(NOTICE, "exception (%d,%d) restore at %s:%d",\
PLy_call_level, exc_save_calls, __FUNCTION__, (__LINE__));\
exc_save_calls -= 1; \
memcpy(&Warn_restart, &(buf_##N), sizeof(sigjmp_buf)); } while (0)
# define SAVE_N_EXC(N) do { \
exc_save_calls += 1; \
elog(NOTICE, "exception (%d,%d) save at %s:%d", \
PLy_call_level, exc_save_calls, __FUNCTION__, (__LINE__)); \
memcpy(&(buf_##N), &Warn_restart, sizeof(sigjmp_buf)); } while (0)
# define RERAISE_N_EXC(N) do { \
elog(NOTICE, "exception (%d,%d) reraise at %s:%d", \
PLy_call_level, exc_save_calls, __FUNCTION__, (__LINE__)); \
siglongjmp(Warn_restart, rv_##N); } while (0)
#define RAISE_EXC(V) do { \
elog(NOTICE, "exception (%d,%d) raise at %s:%d", \
PLy_call_level, exc_save_calls, __FUNCTION__, (__LINE__)); \
siglongjmp(Warn_restart, (V)); } while (0)
#endif
#define DECLARE_EXC() DECLARE_N_EXC(save_restart)
#define SAVE_EXC() SAVE_N_EXC(save_restart)
#define RERAISE_EXC() RERAISE_N_EXC(save_restart)
#define RESTORE_EXC() RESTORE_N_EXC(save_restart)
#define TRAP_EXC() TRAP_N_EXC(save_restart)
#if DEBUG_LEVEL
# define CALL_LEVEL_INC() do { PLy_call_level += 1; \
elog(NOTICE, "Level: %d", PLy_call_level); } while (0)
# define CALL_LEVEL_DEC() do { elog(NOTICE, "Level: %d", PLy_call_level); \
PLy_call_level -= 1; } while (0)
#else
# define CALL_LEVEL_INC() do { PLy_call_level += 1; } while (0)
# define CALL_LEVEL_DEC() do { PLy_call_level -= 1; } while (0)
#endif
/* temporary debugging macros
*/
#if DEBUG_LEVEL
# define enter() elog(NOTICE, "Enter(%d): %s", func_enter_calls++,__FUNCTION__)
# define leave() elog(NOTICE, "Leave(%d): %s", func_leave_calls++,__FUNCTION__)
# define mark() elog(NOTICE, "Mark: %s:%d", __FUNCTION__, __LINE__);
# define refc(O) elog(NOTICE, "Ref<%p>:<%d>:%s:%d", (O), (((O) == NULL) ? -1 : (O)->ob_refcnt), __FUNCTION__, __LINE__)
#else
# define enter()
# define leave()
# define mark()
# define refc(O)
#endif
#endif

View File

@ -0,0 +1,9 @@
CREATE FUNCTION plpython_call_handler() RETURNS opaque
AS '/usr/local/lib/postgresql/langs/plpython.so'
LANGUAGE 'c';
CREATE TRUSTED PROCEDURAL LANGUAGE 'plpython'
HANDLER plpython_call_handler
LANCOMPILER 'plpython';

View File

@ -0,0 +1,2 @@
DELETE FROM users ;

View File

@ -0,0 +1,17 @@
DROP INDEX xsequences_pid_idx ;
DROP TABLE xsequences ;
DROP INDEX sequences_product_idx ;
DROP TABLE sequences ;
DROP SEQUENCE sequences_pid_seq ;
DROP TABLE taxonomy ;
DROP SEQUENCE taxonomy_id_seq ;
DROP TABLE entry ;
DROP SEQUENCE entry_eid_seq ;
DROP INDEX logins_userid_idx ;
DROP TABLE logins;
DROP INDEX users_username_idx ;
DROP INDEX users_fname_idx ;
DROP INDEX users_lname_idx ;
DROP INDEX users_userid_idx ;
DROP TABLE users ;
DROP SEQUENCE users_userid_seq ;

View File

@ -0,0 +1,11 @@
DROP FUNCTION plglobals() ;
DROP FUNCTION plstatic() ;
DROP FUNCTION plfail() ;
DROP TRIGGER users_insert_trig on users ;
DROP FUNCTION users_insert() ;
DROP TRIGGER users_update_trig on users ;
DROP FUNCTION users_update() ;
DROP TRIGGER users_delete_trig on users ;
DROP FUNCTION users_delete() ;
DROP PROCEDURAL LANGUAGE 'plpython' ;
DROP FUNCTION plpython_call_handler() ;

View File

@ -0,0 +1,9 @@
-- test error handling, i forgot to restore Warn_restart in
-- the trigger handler once. the errors and subsequent core dump were
-- interesting.
SELECT invalid_type_uncaught('rick');
SELECT invalid_type_caught('rick');
SELECT invalid_type_reraised('rick');
SELECT valid_type('rick');

View File

@ -0,0 +1,291 @@
CREATE FUNCTION global_test_one() returns text
AS
'if not SD.has_key("global_test"):
SD["global_test"] = "set by global_test_one"
if not GD.has_key("global_test"):
GD["global_test"] = "set by global_test_one"
return "SD: " + SD["global_test"] + ", GD: " + GD["global_test"]'
LANGUAGE 'plpython';
CREATE FUNCTION global_test_two() returns text
AS
'if not SD.has_key("global_test"):
SD["global_test"] = "set by global_test_two"
if not GD.has_key("global_test"):
GD["global_test"] = "set by global_test_two"
return "SD: " + SD["global_test"] + ", GD: " + GD["global_test"]'
LANGUAGE 'plpython';
CREATE FUNCTION static_test() returns int4
AS
'if SD.has_key("call"):
SD["call"] = SD["call"] + 1
else:
SD["call"] = 1
return SD["call"]
'
LANGUAGE 'plpython';
-- import python modules
CREATE FUNCTION import_fail() returns text
AS
'try:
import socket
except Exception, ex:
plpy.notice("import socket failed -- %s" % str(ex))
return "failed as expected"
return "succeeded, that wasn''t supposed to happen"'
LANGUAGE 'plpython';
CREATE FUNCTION import_succeed() returns text
AS
'try:
import array
import bisect
import calendar
import cmath
import errno
import math
import md5
import operator
import random
import re
import sha
import string
import time
import whrandom
except Exception, ex:
plpy.notice("import failed -- %s" % str(ex))
return "failed, that wasn''t supposed to happen"
return "succeeded, as expected"'
LANGUAGE 'plpython';
CREATE FUNCTION import_test_one(text) RETURNS text
AS
'import sha
digest = sha.new(args[0])
return digest.hexdigest()'
LANGUAGE 'plpython';
CREATE FUNCTION import_test_two(users) RETURNS text
AS
'import sha
plain = args[0]["fname"] + args[0]["lname"]
digest = sha.new(plain);
return "sha hash of " + plain + " is " + digest.hexdigest()'
LANGUAGE 'plpython';
CREATE FUNCTION argument_test_one(users, text, text) RETURNS text
AS
'words = args[1] + " " + args[2] + " => " + str(args[0])
return words'
LANGUAGE 'plpython';
-- these triggers are dedicated to HPHC of RI who
-- decided that my kid's name was william not willem, and
-- vigorously resisted all efforts at correction. they have
-- since gone bankrupt...
CREATE FUNCTION users_insert() returns opaque
AS
'if TD["new"]["fname"] == None or TD["new"]["lname"] == None:
return "SKIP"
if TD["new"]["username"] == None:
TD["new"]["username"] = TD["new"]["fname"][:1] + "_" + TD["new"]["lname"]
rv = "MODIFY"
else:
rv = None
if TD["new"]["fname"] == "william":
TD["new"]["fname"] = TD["args"][0]
rv = "MODIFY"
return rv'
LANGUAGE 'plpython';
CREATE FUNCTION users_update() returns opaque
AS
'if TD["event"] == "UPDATE":
if TD["old"]["fname"] != TD["new"]["fname"] and TD["old"]["fname"] == TD["args"][0]:
return "SKIP"
return None'
LANGUAGE 'plpython';
CREATE FUNCTION users_delete() RETURNS opaque
AS
'if TD["old"]["fname"] == TD["args"][0]:
return "SKIP"
return None'
LANGUAGE 'plpython';
CREATE TRIGGER users_insert_trig BEFORE INSERT ON users FOR EACH ROW
EXECUTE PROCEDURE users_insert ('willem');
CREATE TRIGGER users_update_trig BEFORE UPDATE ON users FOR EACH ROW
EXECUTE PROCEDURE users_update ('willem');
CREATE TRIGGER users_delete_trig BEFORE DELETE ON users FOR EACH ROW
EXECUTE PROCEDURE users_delete ('willem');
-- nested calls
--
CREATE FUNCTION nested_call_one(text) RETURNS text
AS
'q = "SELECT nested_call_two(''%s'')" % args[0]
r = plpy.execute(q)
return r[0]'
LANGUAGE 'plpython' ;
CREATE FUNCTION nested_call_two(text) RETURNS text
AS
'q = "SELECT nested_call_three(''%s'')" % args[0]
r = plpy.execute(q)
return r[0]'
LANGUAGE 'plpython' ;
CREATE FUNCTION nested_call_three(text) RETURNS text
AS
'return args[0]'
LANGUAGE 'plpython' ;
-- some spi stuff
CREATE FUNCTION spi_prepared_plan_test_one(text) RETURNS text
AS
'if not SD.has_key("myplan"):
q = "SELECT count(*) FROM users WHERE lname = $1"
SD["myplan"] = plpy.prepare(q, [ "text" ])
try:
rv = plpy.execute(SD["myplan"], [args[0]])
return "there are " + str(rv[0]["count"]) + " " + str(args[0]) + "s"
except Exception, ex:
plpy.error(str(ex))
return None
'
LANGUAGE 'plpython';
CREATE FUNCTION spi_prepared_plan_test_nested(text) RETURNS text
AS
'if not SD.has_key("myplan"):
q = "SELECT spi_prepared_plan_test_one(''%s'') as count" % args[0]
SD["myplan"] = plpy.prepare(q)
try:
rv = plpy.execute(SD["myplan"])
if len(rv):
return rv[0]["count"]
except Exception, ex:
plpy.error(str(ex))
return None
'
LANGUAGE 'plpython';
/* really stupid function just to get the module loaded
*/
CREATE FUNCTION stupid() RETURNS text AS 'return "zarkon"' LANGUAGE 'plpython';
/* a typo
*/
CREATE FUNCTION invalid_type_uncaught(text) RETURNS text
AS
'if not SD.has_key("plan"):
q = "SELECT fname FROM users WHERE lname = $1"
SD["plan"] = plpy.prepare(q, [ "test" ])
rv = plpy.execute(SD["plan"], [ args[0] ])
if len(rv):
return rv[0]["fname"]
return None
'
LANGUAGE 'plpython';
/* for what it's worth catch the exception generated by
* the typo, and return None
*/
CREATE FUNCTION invalid_type_caught(text) RETURNS text
AS
'if not SD.has_key("plan"):
q = "SELECT fname FROM users WHERE lname = $1"
try:
SD["plan"] = plpy.prepare(q, [ "test" ])
except plpy.SPIError, ex:
plpy.notice(str(ex))
return None
rv = plpy.execute(SD["plan"], [ args[0] ])
if len(rv):
return rv[0]["fname"]
return None
'
LANGUAGE 'plpython';
/* for what it's worth catch the exception generated by
* the typo, and reraise it as a plain error
*/
CREATE FUNCTION invalid_type_reraised(text) RETURNS text
AS
'if not SD.has_key("plan"):
q = "SELECT fname FROM users WHERE lname = $1"
try:
SD["plan"] = plpy.prepare(q, [ "test" ])
except plpy.SPIError, ex:
plpy.error(str(ex))
rv = plpy.execute(SD["plan"], [ args[0] ])
if len(rv):
return rv[0]["fname"]
return None
'
LANGUAGE 'plpython';
/* no typo no messing about
*/
CREATE FUNCTION valid_type(text) RETURNS text
AS
'if not SD.has_key("plan"):
SD["plan"] = plpy.prepare("SELECT fname FROM users WHERE lname = $1", [ "text" ])
rv = plpy.execute(SD["plan"], [ args[0] ])
if len(rv):
return rv[0]["fname"]
return None
'
LANGUAGE 'plpython';
/* check the handling of uncaught python exceptions
*/
CREATE FUNCTION exception_index_invalid(text) RETURNS text
AS
'return args[1]'
LANGUAGE 'plpython';
/* check handling of nested exceptions
*/
CREATE FUNCTION exception_index_invalid_nested() RETURNS text
AS
'rv = plpy.execute("SELECT test5(''foo'')")
return rv[0]'
LANGUAGE 'plpython';
CREATE FUNCTION join_sequences(sequences) RETURNS text
AS
'if not args[0]["multipart"]:
return args[0]["sequence"]
q = "SELECT sequence FROM xsequences WHERE pid = ''%s''" % args[0]["pid"]
rv = plpy.execute(q)
seq = args[0]["sequence"]
for r in rv:
seq = seq + r["sequence"]
return seq
'
LANGUAGE 'plpython';

View File

@ -0,0 +1,28 @@
INSERT INTO users (fname, lname, username) VALUES ('jane', 'doe', 'j_doe');
INSERT INTO users (fname, lname, username) VALUES ('john', 'doe', 'johnd');
INSERT INTO users (fname, lname, username) VALUES ('willem', 'doe', 'w_doe');
INSERT INTO users (fname, lname, username) VALUES ('rick', 'smith', 'slash');
-- multi table tests
--
INSERT INTO taxonomy (name) VALUES ('HIV I') ;
INSERT INTO taxonomy (name) VALUES ('HIV II') ;
INSERT INTO taxonomy (name) VALUES ('HCV') ;
INSERT INTO entry (accession, txid) VALUES ('A00001', '1') ;
INSERT INTO entry (accession, txid) VALUES ('A00002', '1') ;
INSERT INTO entry (accession, txid) VALUES ('A00003', '1') ;
INSERT INTO entry (accession, txid) VALUES ('A00004', '2') ;
INSERT INTO entry (accession, txid) VALUES ('A00005', '2') ;
INSERT INTO entry (accession, txid) VALUES ('A00006', '3') ;
INSERT INTO sequences (sequence, eid, product, multipart) VALUES ('ABCDEF', 1, 'env', 'true') ;
INSERT INTO xsequences (sequence, pid) VALUES ('GHIJKL', 1) ;
INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 2, 'env') ;
INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 3, 'env') ;
INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 4, 'gag') ;
INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 5, 'env') ;
INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 6, 'ns1') ;

View File

@ -0,0 +1,42 @@
CREATE TABLE users (
fname text not null,
lname text not null,
username text,
userid serial,
PRIMARY KEY(lname, fname)
) ;
CREATE INDEX users_username_idx ON users(username);
CREATE INDEX users_fname_idx ON users(fname);
CREATE INDEX users_lname_idx ON users(lname);
CREATE INDEX users_userid_idx ON users(userid);
CREATE TABLE taxonomy (
id serial primary key,
name text unique
) ;
CREATE TABLE entry (
accession text not null primary key,
eid serial,
txid int2 not null references taxonomy(id)
) ;
CREATE TABLE sequences (
eid int4 not null references entry(eid),
pid serial primary key,
product text not null,
sequence text not null,
multipart bool default 'false'
) ;
CREATE INDEX sequences_product_idx ON sequences(product) ;
CREATE TABLE xsequences (
pid int4 not null references sequences(pid),
sequence text not null
) ;
CREATE INDEX xsequences_pid_idx ON xsequences(pid) ;

View File

@ -0,0 +1,11 @@
CREATE FUNCTION test_setof() returns setof text
AS
'if GD.has_key("calls"):
GD["calls"] = GD["calls"] + 1
if GD["calls"] > 2:
return None
else:
GD["calls"] = 1
return str(GD["calls"])'
LANGUAGE 'plpython';

View File

@ -0,0 +1,63 @@
-- first some tests of basic functionality
--
-- better succeed
--
select stupid();
-- check static and global data
--
SELECT static_test();
SELECT static_test();
SELECT global_test_one();
SELECT global_test_two();
-- import python modules
--
SELECT import_fail();
SELECT import_succeed();
-- test import and simple argument handling
--
SELECT import_test_one('sha hash of this string');
-- test import and tuple argument handling
--
select import_test_two(users) from users where fname = 'willem';
-- test multiple arguments
--
select argument_test_one(users, fname, lname) from users where lname = 'doe';
-- spi and nested calls
--
select nested_call_one('pass this along');
select spi_prepared_plan_test_one('doe');
select spi_prepared_plan_test_one('smith');
select spi_prepared_plan_test_nested('smith');
-- quick peek at the table
--
SELECT * FROM users;
-- should fail
--
UPDATE users SET fname = 'william' WHERE fname = 'willem';
-- should modify william to willem and create username
--
INSERT INTO users (fname, lname) VALUES ('william', 'smith');
INSERT INTO users (fname, lname, username) VALUES ('charles', 'darwin', 'beagle');
SELECT * FROM users;
SELECT join_sequences(sequences) FROM sequences;
SELECT join_sequences(sequences) FROM sequences
WHERE join_sequences(sequences) ~* '^A';
SELECT join_sequences(sequences) FROM sequences
WHERE join_sequences(sequences) ~* '^B';
-- error in trigger
--

16
src/pl/plpython/test.log Normal file
View File

@ -0,0 +1,16 @@
DROP DATABASE
CREATE DATABASE
NOTICE: CREATE TABLE will create implicit sequence 'users_userid_seq' for SERIAL column 'users.userid'
NOTICE: CREATE TABLE/UNIQUE will create implicit index 'users_userid_key' for table 'users'
NOTICE: CREATE TABLE/PRIMARY KEY will create implicit index 'users_pkey' for table 'users'
NOTICE: CREATE TABLE will create implicit sequence 'taxonomy_id_seq' for SERIAL column 'taxonomy.id'
NOTICE: CREATE TABLE/PRIMARY KEY will create implicit index 'taxonomy_pkey' for table 'taxonomy'
NOTICE: CREATE TABLE/UNIQUE will create implicit index 'taxonomy_name_key' for table 'taxonomy'
NOTICE: CREATE TABLE will create implicit sequence 'entry_eid_seq' for SERIAL column 'entry.eid'
NOTICE: CREATE TABLE/PRIMARY KEY will create implicit index 'entry_pkey' for table 'entry'
NOTICE: CREATE TABLE/UNIQUE will create implicit index 'entry_eid_key' for table 'entry'
NOTICE: CREATE TABLE will create implicit trigger(s) for FOREIGN KEY check(s)
NOTICE: CREATE TABLE will create implicit sequence 'sequences_pid_seq' for SERIAL column 'sequences.pid'
NOTICE: CREATE TABLE/PRIMARY KEY will create implicit index 'sequences_pkey' for table 'sequences'
NOTICE: CREATE TABLE will create implicit trigger(s) for FOREIGN KEY check(s)
NOTICE: CREATE TABLE will create implicit trigger(s) for FOREIGN KEY check(s)

52
src/pl/plpython/test.sh Executable file
View File

@ -0,0 +1,52 @@
#!/bin/sh
DBNAME=pltest
DBUSER=postgres
PATH=$PATH:/usr/local/pgsql/bin
export DBNAME DBUSER
echo -n "*** Destroy $DBNAME."
dropdb -U$DBUSER $DBNAME > test.log 2>&1
echo " Done. ***"
echo -n "*** Create $DBNAME."
createdb -U$DBUSER $DBNAME >> test.log 2>&1
echo " Done. ***"
echo -n "*** Create plpython."
psql -U$DBUSER -q $DBNAME < plpython_create.sql >> test.log 2>&1
echo " Done. ***"
echo -n "*** Create tables"
psql -U$DBUSER -q $DBNAME < plpython_schema.sql >> test.log 2>&1
echo -n ", data"
psql -U$DBUSER -q $DBNAME < plpython_populate.sql >> test.log 2>&1
echo -n ", and functions and triggers."
psql -U$DBUSER -q $DBNAME < plpython_function.sql >> test.log 2>&1
echo " Done. ***"
echo -n "*** Running feature tests."
psql -U$DBUSER -q -e $DBNAME < plpython_test.sql > feature.output 2>&1
echo " Done. ***"
echo -n "*** Running error handling tests."
psql -U$DBUSER -q -e $DBNAME < plpython_error.sql > error.output 2>&1
echo " Done. ***"
echo -n "*** Checking the results of the feature tests"
if diff -u feature.expected feature.output > feature.diff 2>&1 ; then
echo -n " passed!"
else
echo -n " failed! Please examine feature.diff."
fi
echo " Done. ***"
echo -n "*** Checking the results of the error handling tests."
diff -u error.expected error.output > error.diff 2>&1
echo " Done. ***"
echo "*** You need to check the file error.diff and make sure that"
echo " any differences are due only to the oid encoded in the "
echo " python function name. ***"
# or write a fancier error checker...

5
src/pl/plpython/update.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
cd /usr/local/lib/postgresql/langs
cp /home/andrew/projects/pg/plpython/plpython.so ./
cd /home/andrew/projects/pg/plpython