mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-01-30 19:00:29 +08:00
Add plpython code.
This commit is contained in:
parent
6319f5da37
commit
0bef7ba549
@ -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
69
src/pl/plpython/Makefile
Normal 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
171
src/pl/plpython/README
Normal 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?
|
12
src/pl/plpython/dynloader.diff
Normal file
12
src/pl/plpython/dynloader.diff
Normal 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
|
18
src/pl/plpython/error.diff
Normal file
18
src/pl/plpython/error.diff
Normal 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
|
19
src/pl/plpython/error.expected
Normal file
19
src/pl/plpython/error.expected
Normal 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)
|
||||
|
19
src/pl/plpython/error.output
Normal file
19
src/pl/plpython/error.output
Normal 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)
|
||||
|
11
src/pl/plpython/feature.diff
Normal file
11
src/pl/plpython/feature.diff
Normal 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
|
139
src/pl/plpython/feature.expected
Normal file
139
src/pl/plpython/feature.expected
Normal 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)
|
||||
|
139
src/pl/plpython/feature.output
Normal file
139
src/pl/plpython/feature.output
Normal 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
44
src/pl/plpython/linux.h
Normal 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
2623
src/pl/plpython/plpython.c
Normal file
File diff suppressed because it is too large
Load Diff
66
src/pl/plpython/plpython.h
Normal file
66
src/pl/plpython/plpython.h
Normal 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
|
9
src/pl/plpython/plpython_create.sql
Normal file
9
src/pl/plpython/plpython_create.sql
Normal 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';
|
||||
|
2
src/pl/plpython/plpython_depopulate.sql
Normal file
2
src/pl/plpython/plpython_depopulate.sql
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
DELETE FROM users ;
|
17
src/pl/plpython/plpython_deschema.sql
Normal file
17
src/pl/plpython/plpython_deschema.sql
Normal 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 ;
|
11
src/pl/plpython/plpython_drop.sql
Normal file
11
src/pl/plpython/plpython_drop.sql
Normal 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() ;
|
9
src/pl/plpython/plpython_error.sql
Normal file
9
src/pl/plpython/plpython_error.sql
Normal 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');
|
291
src/pl/plpython/plpython_function.sql
Normal file
291
src/pl/plpython/plpython_function.sql
Normal 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';
|
||||
|
||||
|
||||
|
28
src/pl/plpython/plpython_populate.sql
Normal file
28
src/pl/plpython/plpython_populate.sql
Normal 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') ;
|
42
src/pl/plpython/plpython_schema.sql
Normal file
42
src/pl/plpython/plpython_schema.sql
Normal 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) ;
|
||||
|
||||
|
11
src/pl/plpython/plpython_setof.sql
Normal file
11
src/pl/plpython/plpython_setof.sql
Normal 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';
|
63
src/pl/plpython/plpython_test.sql
Normal file
63
src/pl/plpython/plpython_test.sql
Normal 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
16
src/pl/plpython/test.log
Normal 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
52
src/pl/plpython/test.sh
Executable 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
5
src/pl/plpython/update.sh
Executable 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
|
Loading…
Reference in New Issue
Block a user