Commit 0bef7ba5 authored by Bruce Momjian's avatar Bruce Momjian

Add plpython code.

parent 6319f5da
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
# #
# Copyright (c) 1994, Regents of the University of California # 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) ...@@ -22,6 +22,10 @@ ifeq ($(with_perl), yes)
DIRS += plperl DIRS += plperl
endif endif
ifeq ($(with_python), yes)
DIRS += plpython
endif
all install installdirs uninstall depend distprep: all install installdirs uninstall depend distprep:
@for dir in $(DIRS); do $(MAKE) -C $$dir $@ || exit; done @for dir in $(DIRS); do $(MAKE) -C $$dir $@ || exit; done
......
# 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
*** 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?
--- 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
--- 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
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)
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)
--- 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
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)
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)
/*-------------------------------------------------------------------------
*
* 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 */
/* -*- C -*-
*
* plpython.c - python as a procedural language for PostgreSQL
*
* IDENTIFICATION
*
* This software is copyright by Andrew Bosma
* but is really shameless cribbed from pltcl.c by Jan Weick, and
* plperl.c by Mark Hollomon.
*
* The author hereby grants permission to use, copy, modify,
* distribute, and license this software and its documentation for any
* purpose, provided that existing copyright notices are retained in
* all copies and that this notice is included verbatim in any
* distributions. No written agreement, license, or royalty fee is
* required for any of the authorized uses. Modifications to this
* software may be copyrighted by their author and need not follow the
* licensing terms described here, provided that the new terms are
* clearly indicated on the first page of each file where they apply.
*
* IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY PARTY
* FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
* DERIVATIVES THEREOF, EVEN IF THE AUTHOR HAVE BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
* NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
* AND THE AUTHOR AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE
* MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
**********************************************************************/
/* system stuff
*/
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <setjmp.h>
/* postgreSQL stuff
*/
#include "executor/spi.h"
#include "commands/trigger.h"
#include "utils/elog.h"
#include "fmgr.h"
#include "access/heapam.h"
#include "tcop/tcopprot.h"
#include "utils/syscache.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include <Python.h>
#include "plpython.h"
/* convert Postgresql Datum or tuple into a PyObject.
* input to Python. Tuples are converted to dictionary
* objects.
*/
typedef PyObject *(*PLyDatumToObFunc) (const char *);
typedef struct PLyDatumToOb {
PLyDatumToObFunc func;
FmgrInfo typfunc;
Oid typoutput;
Oid typelem;
int2 typlen;
} PLyDatumToOb;
typedef struct PLyTupleToOb {
PLyDatumToOb *atts;
int natts;
} PLyTupleToOb;
typedef union PLyTypeInput {
PLyDatumToOb d;
PLyTupleToOb r;
} PLyTypeInput;
/* convert PyObject to a Postgresql Datum or tuple.
* output from Python
*/
typedef struct PLyObToDatum {
FmgrInfo typfunc;
Oid typelem;
int2 typlen;
} PLyObToDatum;
typedef struct PLyObToTuple {
PLyObToDatum *atts;
int natts;
} PLyObToTuple;
typedef union PLyTypeOutput {
PLyObToDatum d;
PLyObToTuple r;
} PLyTypeOutput;
/* all we need to move Postgresql data to Python objects,
* and vis versa
*/
typedef struct PLyTypeInfo {
PLyTypeInput in;
PLyTypeOutput out;
int is_rel;
} PLyTypeInfo;
/* cached procedure data
*/
typedef struct PLyProcedure {
char *proname;
PLyTypeInfo result; /* also used to store info for trigger tuple type */
PLyTypeInfo args[FUNC_MAX_ARGS];
int nargs;
PyObject *interp; /* restricted interpreter instance */
PyObject *reval; /* interpreter return */
PyObject *code; /* compiled procedure code */
PyObject *statics; /* data saved across calls, local scope */
PyObject *globals; /* data saved across calls, global score */
PyObject *me; /* PyCObject containing pointer to this PLyProcedure */
} PLyProcedure;
/* Python objects.
*/
typedef struct PLyPlanObject {
PyObject_HEAD;
void *plan; /* return of an SPI_saveplan */
int nargs;
Oid *types;
Datum *values;
PLyTypeInfo *args;
} PLyPlanObject;
typedef struct PLyResultObject {
PyObject_HEAD;
/* HeapTuple *tuples; */
PyObject *nrows; /* number of rows returned by query */
PyObject *rows; /* data rows, or None if no data returned */
PyObject *status; /* query status, SPI_OK_*, or SPI_ERR_* */
} PLyResultObject;
/* function declarations
*/
/* the only exported function, with the magic telling Postgresql
* what function call interface it implements.
*/
Datum plpython_call_handler(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(plpython_call_handler);
/* most of the remaining of the declarations, all static
*/
/* these should only be called once at the first call
* of plpython_call_handler. initialize the python interpreter
* and global data.
*/
static void PLy_init_all(void);
static void PLy_init_interp(void);
static void PLy_init_safe_interp(void);
static void PLy_init_plpy(void);
/* error handler. collects the current Python exception, if any,
* and appends it to the error and sends it to elog
*/
static void PLy_elog(int, const char *, ...);
/* call PyErr_SetString with a vprint interface
*/
static void PLy_exception_set(PyObject *, const char *, ...)
__attribute__ ((format (printf, 2, 3)));
/* some utility functions
*/
static void *PLy_malloc(size_t);
static void *PLy_realloc(void *, size_t);
static void PLy_free(void *);
/* sub handlers for functions and triggers
*/
static Datum PLy_function_handler(PG_FUNCTION_ARGS, PLyProcedure *);
static HeapTuple PLy_trigger_handler(PG_FUNCTION_ARGS, PLyProcedure *);
static PyObject *PLy_function_build_args(PG_FUNCTION_ARGS, PLyProcedure *);
static PyObject *PLy_trigger_build_args(PG_FUNCTION_ARGS, PLyProcedure *,
HeapTuple *);
static HeapTuple PLy_modify_tuple(PLyProcedure *, PyObject *,
TriggerData *, HeapTuple);
static PyObject *PLy_procedure_call(PLyProcedure *, char *, PyObject *);
/* returns a cached PLyProcedure, or creates, stores and returns
* a new PLyProcedure.
*/
static PLyProcedure *PLy_procedure_get(PG_FUNCTION_ARGS, bool);
static PLyProcedure *PLy_procedure_create(PG_FUNCTION_ARGS, bool, char *);
static void PLy_procedure_compile(PLyProcedure *, const char *);
static char *PLy_procedure_munge_source(const char *, const char *);
static PLyProcedure *PLy_procedure_new(const char *name);
static void PLy_procedure_delete(PLyProcedure *);
static void PLy_typeinfo_init(PLyTypeInfo *);
static void PLy_typeinfo_dealloc(PLyTypeInfo *);
static void PLy_output_datum_func(PLyTypeInfo *, Form_pg_type);
static void PLy_output_datum_func2(PLyObToDatum *, Form_pg_type);
static void PLy_input_datum_func(PLyTypeInfo *, Form_pg_type);
static void PLy_input_datum_func2(PLyDatumToOb *, Form_pg_type);
static void PLy_output_tuple_funcs(PLyTypeInfo *, TupleDesc);
static void PLy_input_tuple_funcs(PLyTypeInfo *, TupleDesc);
/* conversion functions
*/
static PyObject *PLyDict_FromTuple(PLyTypeInfo *, HeapTuple, TupleDesc);
static PyObject *PLyBool_FromString(const char *);
static PyObject *PLyFloat_FromString(const char *);
static PyObject *PLyInt_FromString(const char *);
static PyObject *PLyString_FromString(const char *);
/* global data
*/
static int PLy_first_call = 1;
static volatile int PLy_call_level = 0;
/* this gets modified in plpython_call_handler and PLy_elog.
* test it any old where, but do NOT modify it anywhere except
* those two functions
*/
static volatile int PLy_restart_in_progress = 0;
static PyObject *PLy_interp_globals = NULL;
static PyObject *PLy_interp_safe = NULL;
static PyObject *PLy_interp_safe_globals = NULL;
static PyObject *PLy_importable_modules = NULL;
static PyObject *PLy_procedure_cache = NULL;
static char *PLy_procedure_fmt = "__plpython_procedure_%s_%u";
char *PLy_importable_modules_list[] = {
"array",
"bisect",
"calendar",
"cmath",
"errno",
"marshal",
"math",
"md5",
"mpz",
"operator",
"pickle",
"random",
"re",
"sha",
"string",
"StringIO",
"time",
"whrandom",
"zlib"
};
/* Python exceptions
*/
PyObject *PLy_exc_error = NULL;
PyObject *PLy_exc_fatal = NULL;
PyObject *PLy_exc_spi_error = NULL;
/* some globals for the python module
*/
static char PLy_plan_doc[] = {
"Store a PostgreSQL plan"
};
static char PLy_result_doc[] = {
"Results of a PostgreSQL query"
};
#if DEBUG_EXC
volatile int exc_save_calls = 0;
volatile int exc_restore_calls = 0;
volatile int func_enter_calls = 0;
volatile int func_leave_calls = 0;
#endif
/* the function definitions
*/
Datum
plpython_call_handler(PG_FUNCTION_ARGS)
{
DECLARE_EXC();
Datum retval;
bool is_trigger;
PLyProcedure *volatile proc = NULL;
enter();
if (PLy_first_call)
PLy_init_all();
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "plpython: Unable to connect to SPI manager");
CALL_LEVEL_INC();
is_trigger = CALLED_AS_TRIGGER(fcinfo);
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
CALL_LEVEL_DEC();
if (PLy_call_level == 0)
{
PLy_restart_in_progress = 0;
PyErr_Clear();
}
else
PLy_restart_in_progress += 1;
if (proc)
{ Py_DECREF(proc->me); }
RERAISE_EXC();
}
/*elog(NOTICE, "PLy_restart_in_progress is %d", PLy_restart_in_progress);*/
proc = PLy_procedure_get(fcinfo, is_trigger);
if (is_trigger)
{
HeapTuple trv = PLy_trigger_handler(fcinfo, proc);
retval = PointerGetDatum(trv);
}
else
retval = PLy_function_handler(fcinfo, proc);
CALL_LEVEL_DEC();
RESTORE_EXC();
Py_DECREF(proc->me);
refc(proc->me);
return retval;
}
/* trigger and function sub handlers
*
* the python function is expected to return Py_None if the tuple is
* acceptable and unmodified. Otherwise it should return a PyString
* object who's value is SKIP, or MODIFY. SKIP means don't perform
* this action. MODIFY means the tuple has been modified, so update
* tuple and perform action. SKIP and MODIFY assume the trigger fires
* BEFORE the event and is ROW level. postgres expects the function
* to take no arguments and return an argument of type opaque.
*/
HeapTuple
PLy_trigger_handler(PG_FUNCTION_ARGS, PLyProcedure *proc)
{
DECLARE_EXC();
HeapTuple rv = NULL;
PyObject *plargs = NULL;
PyObject *plrv = NULL;
enter();
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
Py_XDECREF(plargs);
Py_XDECREF(plrv);
RERAISE_EXC();
}
plargs = PLy_trigger_build_args(fcinfo, proc, &rv);
plrv = PLy_procedure_call(proc, "TD", plargs);
/* Disconnect from SPI manager
*/
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "plpython: SPI_finish failed");
if (plrv == NULL)
elog(FATAL, "Aiieee, PLy_procedure_call returned NULL");
if (PLy_restart_in_progress)
elog(FATAL, "Aiieee, restart in progress not expected");
/* return of None means we're happy with the tuple
*/
if (plrv != Py_None)
{
char *srv;
if (!PyString_Check(plrv))
elog(ERROR, "plpython: Expected trigger to return None or a String");
srv = PyString_AsString(plrv);
if (strcasecmp(srv, "SKIP") == 0)
rv = NULL;
else if (strcasecmp(srv, "MODIFY") == 0)
{
TriggerData *tdata = (TriggerData *) fcinfo->context;
if ((TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) ||
(TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)))
{
rv = PLy_modify_tuple(proc, plargs, tdata, rv);
}
else
elog(NOTICE,"plpython: Ignoring modified tuple in DELETE trigger");
}
else if (strcasecmp(srv, "OK"))
{
/* hmmm, perhaps they only read the pltcl page, not a surprising
* thing since i've written no documentation, so accept a
* belated OK
*/
elog(ERROR, "plpython: Expected return to be 'SKIP' or 'MODIFY'");
}
}
Py_DECREF(plargs);
Py_DECREF(plrv);
RESTORE_EXC();
return rv;
}
HeapTuple
PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
HeapTuple otup)
{
DECLARE_EXC();
PyObject *plntup, *plkeys, *platt, *plval, *plstr;
HeapTuple rtup;
int natts, i, j, attn, atti;
int *modattrs;
Datum *modvalues;
char *modnulls;
TupleDesc tupdesc;
plntup = plkeys = platt = plval = plstr = NULL;
modattrs = NULL;
modvalues = NULL;
modnulls = NULL;
enter();
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
Py_XDECREF(plntup);
Py_XDECREF(plkeys);
Py_XDECREF(platt);
Py_XDECREF(plval);
Py_XDECREF(plstr);
if (modnulls)
pfree(modnulls);
if (modvalues)
pfree(modvalues);
if (modattrs)
pfree(modattrs);
RERAISE_EXC();
}
if ((plntup = PyDict_GetItemString(pltd, "new")) == NULL)
elog(ERROR, "plpython: TD[\"new\"] deleted, unable to modify tuple");
if (!PyDict_Check(plntup))
elog(ERROR, "plpython: TD[\"new\"] is not a dictionary object");
Py_INCREF(plntup);
plkeys = PyDict_Keys(plntup);
natts = PyList_Size(plkeys);
if (natts != proc->result.out.r.natts)
elog(ERROR, "plpython: TD[\"new\"] has an incorrect number of keys.");
modattrs = palloc(natts * sizeof(int));
modvalues = palloc(natts * sizeof(Datum));
for (i = 0; i < natts; i++)
{
modattrs[i] = i + 1;
modvalues[i] = (Datum) NULL;
}
modnulls = palloc(natts + 1);
memset(modnulls, 'n', natts);
modnulls[natts] = '\0';
tupdesc = tdata->tg_relation->rd_att;
for (j = 0; j < natts; j++)
{
char *src;
platt = PyList_GetItem(plkeys, j);
if (!PyString_Check(platt))
elog(ERROR, "plpython: attribute is not a string");
attn = modattrs[j] = SPI_fnumber(tupdesc, PyString_AsString(platt));
if (attn == SPI_ERROR_NOATTRIBUTE)
elog(ERROR, "plpython: invalid attribute `%s' in tuple.",
PyString_AsString(platt));
atti = attn - 1;
plval = PyDict_GetItem(plntup, platt);
if (plval == NULL)
elog(FATAL, "plpython: interpreter is probably corrupted");
Py_INCREF(plval);
if (plval != Py_None)
{
plstr = PyObject_Str(plval);
src = PyString_AsString(plstr);
modvalues[j] = FunctionCall3(&proc->result.out.r.atts[atti].typfunc,
CStringGetDatum(src),
proc->result.out.r.atts[atti].typelem,
proc->result.out.r.atts[atti].typlen);
modnulls[j] = ' ';
Py_DECREF(plstr);
plstr = NULL;
}
Py_DECREF(plval);
plval = NULL;
}
rtup = SPI_modifytuple(tdata->tg_relation, otup, natts, modattrs,
modvalues, modnulls);
/* FIXME -- these leak if not explicity pfree'd by other elog calls, no?
*/
pfree(modattrs);
pfree(modvalues);
pfree(modnulls);
if (rtup == NULL)
elog(ERROR, "plpython: SPI_modifytuple failed -- error %d", SPI_result);
Py_DECREF(plntup);
Py_DECREF(plkeys);
RESTORE_EXC();
return rtup;
}
PyObject *
PLy_trigger_build_args(PG_FUNCTION_ARGS, PLyProcedure *proc, HeapTuple *rv)
{
DECLARE_EXC();
TriggerData *tdata;
PyObject *pltname, *pltevent, *pltwhen, *pltlevel;
PyObject *pltargs, *pytnew, *pytold;
PyObject *pltdata = NULL;
enter();
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
Py_XDECREF(pltdata);
RERAISE_EXC();
}
tdata = (TriggerData *) fcinfo->context;
pltdata = PyDict_New();
if (!pltdata)
PLy_elog(ERROR, "Unable to build arguments for trigger procedure");
pltname = PyString_FromString(tdata->tg_trigger->tgname);
PyDict_SetItemString(pltdata, "name", pltname);
Py_DECREF(pltname);
if (TRIGGER_FIRED_BEFORE(tdata->tg_event))
pltwhen = PyString_FromString("BEFORE");
else if (TRIGGER_FIRED_AFTER(tdata->tg_event))
pltwhen = PyString_FromString("AFTER");
else
pltwhen = PyString_FromString("UNKNOWN");
PyDict_SetItemString(pltdata, "when", pltwhen);
Py_DECREF(pltwhen);
if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event))
pltlevel = PyString_FromString("ROW");
else if (TRIGGER_FIRED_FOR_STATEMENT(tdata->tg_event))
pltlevel = PyString_FromString("STATEMENT");
else
pltlevel = PyString_FromString("UNKNOWN");
PyDict_SetItemString(pltdata, "level", pltlevel);
Py_DECREF(pltlevel);
if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event))
{
pltevent = PyString_FromString("INSERT");
PyDict_SetItemString(pltdata, "old", Py_None);
pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
tdata->tg_relation->rd_att);
PyDict_SetItemString(pltdata, "new", pytnew);
Py_DECREF(pytnew);
*rv = tdata->tg_trigtuple;
}
else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event))
{
pltevent = PyString_FromString("DELETE");
PyDict_SetItemString(pltdata, "new", Py_None);
pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
tdata->tg_relation->rd_att);
PyDict_SetItemString(pltdata, "old", pytold);
Py_DECREF(pytold);
*rv = tdata->tg_trigtuple;
}
else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event))
{
pltevent = PyString_FromString("UPDATE");
pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_newtuple,
tdata->tg_relation->rd_att);
PyDict_SetItemString(pltdata, "new", pytnew);
Py_DECREF(pytnew);
pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
tdata->tg_relation->rd_att);
PyDict_SetItemString(pltdata, "old", pytold);
Py_DECREF(pytold);
*rv = tdata->tg_newtuple;
}
else
{
pltevent = PyString_FromString("UNKNOWN");
PyDict_SetItemString(pltdata, "old", Py_None);
PyDict_SetItemString(pltdata, "new", Py_None);
*rv = tdata->tg_trigtuple;
}
PyDict_SetItemString(pltdata, "event", pltevent);
Py_DECREF(pltevent);
if (tdata->tg_trigger->tgnargs)
{
/* all strings...
*/
int i;
PyObject *pltarg;
pltargs = PyList_New(tdata->tg_trigger->tgnargs);
for (i = 0; i < tdata->tg_trigger->tgnargs; i++)
{
pltarg = PyString_FromString(tdata->tg_trigger->tgargs[i]);
/* stolen, don't Py_DECREF
*/
PyList_SetItem(pltargs, i, pltarg);
}
}
else
{
Py_INCREF(Py_None);
pltargs = Py_None;
}
PyDict_SetItemString(pltdata, "args", pltargs);
Py_DECREF(pltargs);
RESTORE_EXC();
return pltdata;
}
/* function handler and friends
*/
Datum
PLy_function_handler(PG_FUNCTION_ARGS, PLyProcedure *proc)
{
DECLARE_EXC();
Datum rv;
PyObject *plargs = NULL;
PyObject *plrv = NULL;
PyObject *plrv_so = NULL;
char *plrv_sc;
enter();
/*
* setup to catch elog in while building function arguments,
* and DECREF the plargs if the function call fails
*/
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
Py_XDECREF(plargs);
Py_XDECREF(plrv);
Py_XDECREF(plrv_so);
RERAISE_EXC();
}
plargs = PLy_function_build_args(fcinfo, proc);
plrv = PLy_procedure_call(proc, "args", plargs);
/* Disconnect from SPI manager and then create the return
* values datum (if the input function does a palloc for it
* this must not be allocated in the SPI memory context
* because SPI_finish would free it).
*/
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "plpython: SPI_finish failed");
if (plrv == NULL)
{
elog(FATAL, "Aiieee, PLy_procedure_call returned NULL");
#if 0
if (!PLy_restart_in_progress)
PLy_elog(ERROR, "plpython: Function \"%s\" failed.", proc->proname);
/* FIXME is this dead code? i'm pretty sure it is for unnested
* calls, but not for nested calls
*/
RAISE_EXC(1);
#endif
}
/* convert the python PyObject to a postgresql Datum
* FIXME returning a NULL, ie PG_RETURN_NULL() blows the backend
* to small messy bits... it this a bug or expected? so just
* call with the string value of None for now
*/
if (plrv == Py_None)
{
fcinfo->isnull = true;
rv = (Datum) NULL;
}
else
{
fcinfo->isnull = false;
plrv_so = PyObject_Str(plrv);
plrv_sc = PyString_AsString(plrv_so);
rv = FunctionCall3(&proc->result.out.d.typfunc,
PointerGetDatum(plrv_sc),
proc->result.out.d.typelem,
proc->result.out.d.typlen);
}
RESTORE_EXC();
Py_XDECREF(plargs);
Py_DECREF(plrv);
Py_XDECREF(plrv_so);
return rv;
}
PyObject *
PLy_procedure_call(PLyProcedure *proc, char *kargs, PyObject *vargs)
{
PyObject *rv;
enter();
PyDict_SetItemString(proc->globals, kargs, vargs);
rv = PyObject_CallFunction(proc->reval, "O", proc->code);
if ((rv == NULL) || (PyErr_Occurred()))
{
Py_XDECREF(rv);
if (!PLy_restart_in_progress)
PLy_elog(ERROR, "Call of function `%s' failed.", proc->proname);
RAISE_EXC(1);
}
return rv;
}
PyObject *
PLy_function_build_args(PG_FUNCTION_ARGS, PLyProcedure *proc)
{
DECLARE_EXC();
PyObject *arg = NULL;
PyObject *args = NULL;
int i;
enter();
/* FIXME -- if the setjmp setup is expensive, add the arg and
* args field to the procedure struct and cleanup at the
* start of the next call
*/
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
Py_XDECREF(arg);
Py_XDECREF(args);
RERAISE_EXC();
}
args = PyList_New(proc->nargs);
for (i = 0; i < proc->nargs; i++)
{
if (proc->args[i].is_rel == 1)
{
TupleTableSlot *slot = (TupleTableSlot *) fcinfo->arg[i];
arg = PLyDict_FromTuple(&(proc->args[i]), slot->val,
slot->ttc_tupleDescriptor);
}
else
{
if (!fcinfo->argnull[i])
{
char *ct;
Datum dt;
dt = FunctionCall3(&(proc->args[i].in.d.typfunc),
fcinfo->arg[i],
proc->args[i].in.d.typelem,
proc->args[i].in.d.typlen);
ct = DatumGetCString(dt);
arg = (proc->args[i].in.d.func)(ct);
pfree(ct);
}
else
arg = NULL;
}
if (arg == NULL)
{
Py_INCREF(Py_None);
arg = Py_None;
}
/* FIXME -- error check this
*/
PyList_SetItem(args, i, arg);
}
RESTORE_EXC();
return args;
}
/* PLyProcedure functions
*/
PLyProcedure *
PLy_procedure_get(PG_FUNCTION_ARGS, bool is_trigger)
{
char key[128];
PyObject *plproc;
PLyProcedure *proc;
int rv;
enter();
rv = snprintf(key, sizeof(key), "%u", fcinfo->flinfo->fn_oid);
if ((rv >= sizeof(key)) || (rv < 0))
elog(FATAL, "plpython: Buffer overrun in %s:%d", __FILE__, __LINE__);
plproc = PyDict_GetItemString(PLy_procedure_cache, key);
if (plproc == NULL)
return PLy_procedure_create(fcinfo, is_trigger, key);
Py_INCREF(plproc);
if (!PyCObject_Check(plproc))
elog(FATAL, "plpython: Expected a PyCObject, didn't get one");
mark();
proc = PyCObject_AsVoidPtr(plproc);
if (proc->me != plproc)
elog(FATAL, "plpython: Aiieee, proc->me != plproc");
return proc;
}
PLyProcedure *
PLy_procedure_create(PG_FUNCTION_ARGS, bool is_trigger, char *key)
{
char procName[256];
DECLARE_EXC();
HeapTuple procTup;
Form_pg_proc procStruct;
Oid fn_oid;
PLyProcedure *volatile proc;
char *volatile procSource = NULL;
Datum procDatum;
int i, rv;
enter();
fn_oid = fcinfo->flinfo->fn_oid;
procTup = SearchSysCache(PROCOID, ObjectIdGetDatum(fn_oid), 0, 0, 0);
if (!HeapTupleIsValid(procTup))
elog(ERROR, "plpython: cache lookup for procedure \"%u\" failed", fn_oid);
procStruct = (Form_pg_proc) GETSTRUCT(procTup);
rv = snprintf(procName, sizeof(procName), PLy_procedure_fmt,
NameStr(procStruct->proname), fn_oid);
if ((rv >= sizeof(procName)) || (rv < 0))
elog(FATAL, "plpython: Procedure name would overrun buffer");
proc = PLy_procedure_new(procName);
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
PLy_procedure_delete(proc);
if (procSource)
pfree(procSource);
RERAISE_EXC();
}
/* get information required for output conversion of the return
* value, but only if this isn't a trigger.
*/
if (!is_trigger)
{
HeapTuple rvTypeTup;
Form_pg_type rvTypeStruct;
Datum rvDatum;
rvDatum = ObjectIdGetDatum(procStruct->prorettype);
rvTypeTup = SearchSysCache(TYPEOID, rvDatum, 0, 0, 0);
if (!HeapTupleIsValid(rvTypeTup))
elog(ERROR, "plpython: cache lookup for type \"%u\" failed",
procStruct->prorettype);
rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup);
if (rvTypeStruct->typrelid == InvalidOid)
PLy_output_datum_func(&proc->result, rvTypeStruct);
else
elog(ERROR, "plpython: tuple return types not supported, yet");
ReleaseSysCache(rvTypeTup);
}
else
{
/* input/output conversion for trigger tuples. use the
* result TypeInfo variable to store the tuple conversion
* info.
*/
TriggerData *tdata = (TriggerData *) fcinfo->context;
PLy_input_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att);
PLy_output_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att);
}
/* now get information required for input conversion of the
* procedures arguments.
*/
proc->nargs = fcinfo->nargs;
for (i = 0; i < fcinfo->nargs; i++)
{
HeapTuple argTypeTup;
Form_pg_type argTypeStruct;
Datum argDatum;
argDatum = ObjectIdGetDatum(procStruct->proargtypes[i]);
argTypeTup = SearchSysCache(TYPEOID, argDatum, 0, 0, 0);
if (!HeapTupleIsValid(argTypeTup))
elog(ERROR, "plpython: cache lookup for type \"%u\" failed",
procStruct->proargtypes[i]);
argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup);
if (argTypeStruct->typrelid == InvalidOid)
PLy_input_datum_func(&(proc->args[i]), argTypeStruct);
else
{
TupleTableSlot *slot = (TupleTableSlot *) fcinfo->arg[i];
PLy_input_tuple_funcs(&(proc->args[i]),
slot->ttc_tupleDescriptor);
}
ReleaseSysCache(argTypeTup);
}
/* get the text of the function.
*/
procDatum = DirectFunctionCall1(textout,
PointerGetDatum(&procStruct->prosrc));
procSource = DatumGetCString(procDatum);
ReleaseSysCache(procTup);
PLy_procedure_compile(proc, procSource);
pfree(procSource);
proc->me = PyCObject_FromVoidPtr(proc, NULL);
PyDict_SetItemString(PLy_procedure_cache, key, proc->me);
RESTORE_EXC();
return proc;
}
void
PLy_procedure_compile(PLyProcedure *proc, const char *src)
{
PyObject *module, *crv = NULL;
char *msrc;
enter();
/* get an instance of rexec.RExec for the function
*/
proc->interp = PyObject_CallMethod(PLy_interp_safe, "RExec", NULL);
if ((proc->interp == NULL) || (PyErr_Occurred ()))
PLy_elog(ERROR, "Unable to create rexec.RExec instance");
/* tweak the list of permitted modules
*/
PyObject_SetAttrString(proc->interp, "ok_builtin_modules",
PLy_importable_modules);
proc->reval = PyObject_GetAttrString(proc->interp, "r_eval");
if ((proc->reval == NULL) || (PyErr_Occurred ()))
PLy_elog(ERROR, "Unable to get method `r_eval' from rexec.RExec");
/* add a __main__ module to the function's interpreter
*/
module = PyObject_CallMethod (proc->interp, "add_module", "s", "__main__");
if ((module == NULL) || (PyErr_Occurred ()))
PLy_elog(ERROR, "Unable to get module `__main__' from rexec.RExec");
/* add plpy module to the interpreters main dictionary
*/
proc->globals = PyModule_GetDict (module);
if ((proc->globals == NULL) || (PyErr_Occurred ()))
PLy_elog(ERROR, "Unable to get `__main__.__dict__' from rexec.RExec");
/* why the hell won't r_import or r_exec('import plpy') work?
*/
module = PyDict_GetItemString(PLy_interp_globals, "plpy");
if ((module == NULL) || (PyErr_Occurred()))
PLy_elog(ERROR, "Unable to get `plpy'");
Py_INCREF(module);
PyDict_SetItemString(proc->globals, "plpy", module);
/* SD is private preserved data between calls
* GD is global data shared by all functions
*/
proc->statics = PyDict_New();
PyDict_SetItemString(proc->globals, "SD", proc->statics);
PyDict_SetItemString(proc->globals, "GD", PLy_interp_safe_globals);
/* insert the function code into the interpreter
*/
msrc = PLy_procedure_munge_source(proc->proname, src);
crv = PyObject_CallMethod(proc->interp, "r_exec", "s", msrc);
free(msrc);
if ((crv != NULL) && (!PyErr_Occurred ()))
{
int clen;
char call[256];
Py_DECREF(crv);
/* compile a call to the function
*/
clen = snprintf(call, sizeof(call), "%s()", proc->proname);
if ((clen < 0) || (clen >= sizeof(call)))
elog(ERROR, "plpython: string would overflow buffer.");
proc->code = Py_CompileString(call, "<string>", Py_eval_input);
if ((proc->code != NULL) && (!PyErr_Occurred ()))
return;
}
else
Py_XDECREF(crv);
PLy_elog(ERROR, "Unable to compile function %s", proc->proname);
}
char *
PLy_procedure_munge_source(const char *name, const char *src)
{
char *mrc, *mp;
const char *sp;
size_t mlen, plen;
enter();
/* room for function source and the def statement
*/
mlen = (strlen (src) * 2) + strlen(name) + 16;
mrc = PLy_malloc(mlen);
plen = snprintf(mrc, mlen, "def %s():\n\t", name);
if ((plen < 0) || (plen >= mlen))
elog(FATAL, "Aiieee, impossible buffer overrun (or snprintf failure)");
sp = src;
mp = mrc + plen;
while (*sp != '\0')
{
if (*sp == '\n')
{
*mp++ = *sp++;
*mp++ = '\t';
}
else
*mp++ = *sp++;
}
*mp++ = '\n';
*mp++ = '\n';
*mp = '\0';
if (mp > (mrc + mlen))
elog(FATAL, "plpython: Buffer overrun in PLy_munge_source");
return mrc;
}
PLyProcedure *
PLy_procedure_new(const char *name)
{
int i;
PLyProcedure *proc;
enter();
proc = PLy_malloc(sizeof(PLyProcedure));
proc->proname = PLy_malloc(strlen(name) + 1);
strcpy(proc->proname, name);
PLy_typeinfo_init(&proc->result);
for (i = 0; i < FUNC_MAX_ARGS; i++)
PLy_typeinfo_init(&proc->args[i]);
proc->nargs = 0;
proc->code = proc->interp = proc->reval = proc->statics = NULL;
proc->globals = proc->me = NULL;
leave();
return proc;
}
void
PLy_procedure_delete(PLyProcedure *proc)
{
int i;
enter();
Py_XDECREF(proc->code);
Py_XDECREF(proc->interp);
Py_XDECREF(proc->reval);
Py_XDECREF(proc->statics);
Py_XDECREF(proc->globals);
Py_XDECREF(proc->me);
if (proc->proname)
PLy_free(proc->proname);
for (i = 0; i < proc->nargs; i++)
if (proc->args[i].is_rel == 1)
{
if (proc->args[i].in.r.atts)
PLy_free(proc->args[i].in.r.atts);
if (proc->args[i].out.r.atts)
PLy_free(proc->args[i].out.r.atts);
}
leave();
}
/* conversion functions. remember output from python is
* input to postgresql, and vis versa.
*/
void
PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
{
int i;
Datum datum;
enter ();
if (arg->is_rel == 0)
elog(FATAL, "plpython: PLyTypeInfo struct is initialized for a Datum");
arg->is_rel = 1;
arg->in.r.natts = desc->natts;
arg->in.r.atts = malloc(desc->natts * sizeof(PLyDatumToOb));
for (i = 0; i < desc->natts; i++)
{
HeapTuple typeTup;
Form_pg_type typeStruct;
datum = ObjectIdGetDatum(desc->attrs[i]->atttypid);
typeTup = SearchSysCache(TYPEOID, datum, 0, 0, 0);
if (!HeapTupleIsValid(typeTup))
{
char *attname = NameStr(desc->attrs[i]->attname);
elog(ERROR, "plpython: Cache lookup for attribute `%s' type `%u' failed",
attname, desc->attrs[i]->atttypid);
}
typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
PLy_input_datum_func2(&(arg->in.r.atts[i]), typeStruct);
ReleaseSysCache(typeTup);
}
}
void
PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
{
int i;
Datum datum;
enter ();
if (arg->is_rel == 0)
elog(FATAL, "plpython: PLyTypeInfo struct is initialized for a Datum");
arg->is_rel = 1;
arg->out.r.natts = desc->natts;
arg->out.r.atts = malloc(desc->natts * sizeof(PLyDatumToOb));
for (i = 0; i < desc->natts; i++)
{
HeapTuple typeTup;
Form_pg_type typeStruct;
datum = ObjectIdGetDatum(desc->attrs[i]->atttypid);
typeTup = SearchSysCache(TYPEOID, datum, 0, 0, 0);
if (!HeapTupleIsValid(typeTup))
{
char *attname = NameStr(desc->attrs[i]->attname);
elog(ERROR, "plpython: Cache lookup for attribute `%s' type `%u' failed",
attname, desc->attrs[i]->atttypid);
}
typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
PLy_output_datum_func2(&(arg->out.r.atts[i]), typeStruct);
ReleaseSysCache(typeTup);
}
}
void
PLy_output_datum_func(PLyTypeInfo *arg, Form_pg_type typeStruct)
{
enter();
if (arg->is_rel == 1)
elog(FATAL, "plpython: PLyTypeInfo struct is initialized for a Tuple");
arg->is_rel = 0;
PLy_output_datum_func2(&(arg->out.d), typeStruct);
}
void
PLy_output_datum_func2(PLyObToDatum *arg, Form_pg_type typeStruct)
{
enter();
fmgr_info(typeStruct->typinput, &arg->typfunc);
arg->typelem = (Oid) typeStruct->typelem;
arg->typlen = typeStruct->typlen;
}
void
PLy_input_datum_func(PLyTypeInfo *arg, Form_pg_type typeStruct)
{
enter();
if (arg->is_rel == 1)
elog(FATAL, "plpython: PLyTypeInfo struct is initialized for Tuple");
arg->is_rel = 0;
PLy_input_datum_func2(&(arg->in.d), typeStruct);
}
void
PLy_input_datum_func2(PLyDatumToOb *arg, Form_pg_type typeStruct)
{
char *type;
arg->typoutput = typeStruct->typoutput;
fmgr_info(typeStruct->typoutput, &arg->typfunc);
arg->typlen = typeStruct->typlen;
arg->typelem = typeStruct->typelem;
/* hmmm, wierd. means this arg will always be converted
* to a python None
*/
if (!OidIsValid(typeStruct->typoutput))
{
elog(ERROR, "plpython: (FIXME) typeStruct->typoutput is invalid");
arg->func = NULL;
return;
}
type = NameStr(typeStruct->typname);
switch (type[0])
{
case 'b':
{
if (strcasecmp("bool", type))
{
arg->func = PLyBool_FromString;
return;
}
break;
}
case 'f':
{
if ((strncasecmp("float", type, 5) == 0) &&
((type[5] == '8') || (type[5] == '4')))
{
arg->func = PLyFloat_FromString;
return;
}
break;
}
case 'i':
{
if ((strncasecmp("int", type, 3) == 0) &&
((type[3] == '4') || (type[3] == '2') || (type[3] == '8')) &&
(type[4] == '\0'))
{
arg->func = PLyInt_FromString;
return;
}
break;
}
case 'n':
{
if (strcasecmp("numeric", type) == 0)
{
arg->func = PLyFloat_FromString;
return;
}
break;
}
default:
break;
}
arg->func = PLyString_FromString;
}
void
PLy_typeinfo_init(PLyTypeInfo *arg)
{
arg->is_rel = -1;
arg->in.r.natts = arg->out.r.natts = 0;
arg->in.r.atts = NULL;
arg->out.r.atts = NULL;
}
void
PLy_typeinfo_dealloc(PLyTypeInfo *arg)
{
if (arg->is_rel == 1)
{
if (arg->in.r.atts)
PLy_free(arg->in.r.atts);
if (arg->out.r.atts)
PLy_free(arg->out.r.atts);
}
}
/* assumes that a bool is always returned as a 't' or 'f'
*/
PyObject *
PLyBool_FromString(const char *src)
{
enter();
if (src[0] == 't')
return PyInt_FromLong(1);
return PyInt_FromLong(0);
}
PyObject *
PLyFloat_FromString(const char *src)
{
double v;
char *eptr;
enter();
errno = 0;
v = strtod(src, &eptr);
if ((*eptr != '\0') || (errno))
return NULL;
return PyFloat_FromDouble(v);
}
PyObject *
PLyInt_FromString(const char *src)
{
long v;
char *eptr;
enter();
errno = 0;
v = strtol(src, &eptr, 0);
if ((*eptr != '\0') || (errno))
return NULL;
return PyInt_FromLong(v);
}
PyObject *
PLyString_FromString(const char *src)
{
return PyString_FromString(src);
}
PyObject *
PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc)
{
DECLARE_EXC();
PyObject *volatile dict;
int i;
enter();
if (info->is_rel != 1)
elog(FATAL, "plpython: PLyTypeInfo structure describes a datum.");
dict = PyDict_New();
if (dict == NULL)
PLy_elog(ERROR, "Unable to create tuple dictionary.");
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
Py_DECREF(dict);
RERAISE_EXC();
}
for (i = 0; i < info->in.r.natts; i++)
{
char *key, *vsrc;
Datum vattr, vdat;
bool is_null;
PyObject *value;
key = NameStr(desc->attrs[i]->attname);
vattr = heap_getattr(tuple, (i + 1), desc, &is_null);
if ((is_null) || (info->in.r.atts[i].func == NULL))
PyDict_SetItemString(dict, key, Py_None);
else
{
vdat = OidFunctionCall3(info->in.r.atts[i].typoutput, vattr,
ObjectIdGetDatum(info->in.r.atts[i].typelem),
Int32GetDatum(info->in.r.atts[i].typlen));
vsrc = DatumGetCString(vdat);
/* no exceptions allowed
*/
value = info->in.r.atts[i].func (vsrc);
pfree(vsrc);
PyDict_SetItemString(dict, key, value);
Py_DECREF(value);
}
}
RESTORE_EXC();
return dict;
}
/* initialization, some python variables function declared here
*/
/* interface to postgresql elog
*/
static PyObject *PLy_debug(PyObject *, PyObject *);
static PyObject *PLy_error(PyObject *, PyObject *);
static PyObject *PLy_fatal(PyObject *, PyObject *);
static PyObject *PLy_notice(PyObject *, PyObject *);
/* PLyPlanObject, PLyResultObject and SPI interface
*/
#define is_PLyPlanObject(x) ((x)->ob_type == &PLy_PlanType)
static PyObject *PLy_plan_new(void);
static void PLy_plan_dealloc(PyObject *);
static PyObject *PLy_plan_getattr(PyObject *, char *);
static PyObject *PLy_plan_status(PyObject *, PyObject *);
static PyObject *PLy_result_new(void);
static void PLy_result_dealloc(PyObject *);
static PyObject *PLy_result_getattr(PyObject *, char *);
static PyObject *PLy_result_fetch(PyObject *, PyObject *);
static PyObject *PLy_result_nrows(PyObject *, PyObject *);
static PyObject *PLy_result_status(PyObject *, PyObject *);
static int PLy_result_length(PyObject *);
static PyObject *PLy_result_item(PyObject *, int);
static PyObject *PLy_result_slice(PyObject *, int, int);
static int PLy_result_ass_item(PyObject *, int, PyObject *);
static int PLy_result_ass_slice(PyObject *, int, int, PyObject *);
static PyObject *PLy_spi_prepare(PyObject *, PyObject *);
static PyObject *PLy_spi_execute(PyObject *, PyObject *);
static const char *PLy_spi_error_string(int);
static PyObject *PLy_spi_execute_query(char *query, int limit);
static PyObject *PLy_spi_execute_plan(PyObject *, PyObject *, int);
static PyObject *PLy_spi_execute_fetch_result(SPITupleTable *, int, int);
PyTypeObject PLy_PlanType = {
PyObject_HEAD_INIT(&PyType_Type)
0, /*ob_size*/
"PLyPlan", /*tp_name*/
sizeof(PLyPlanObject), /*tp_size*/
0, /*tp_itemsize*/
/* methods
*/
(destructor) PLy_plan_dealloc, /*tp_dealloc*/
0, /*tp_print*/
(getattrfunc)PLy_plan_getattr, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
0, /*tp_xxx4*/
PLy_plan_doc, /*tp_doc*/
};
PyMethodDef PLy_plan_methods[] = {
{ "status", (PyCFunction) PLy_plan_status, METH_VARARGS, NULL },
{ NULL, NULL, 0, NULL }
};
PySequenceMethods PLy_result_as_sequence = {
(inquiry) PLy_result_length, /* sq_length */
(binaryfunc) 0, /* sq_concat */
(intargfunc) 0, /* sq_repeat */
(intargfunc) PLy_result_item, /* sq_item */
(intintargfunc) PLy_result_slice, /* sq_slice */
(intobjargproc) PLy_result_ass_item, /* sq_ass_item */
(intintobjargproc) PLy_result_ass_slice, /* sq_ass_slice */
};
PyTypeObject PLy_ResultType = {
PyObject_HEAD_INIT(&PyType_Type)
0, /*ob_size*/
"PLyResult", /*tp_name*/
sizeof(PLyResultObject), /*tp_size*/
0, /*tp_itemsize*/
/* methods
*/
(destructor) PLy_result_dealloc, /*tp_dealloc*/
0, /*tp_print*/
(getattrfunc) PLy_result_getattr, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number*/
&PLy_result_as_sequence, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
0, /*tp_xxx4*/
PLy_result_doc, /*tp_doc*/
};
PyMethodDef PLy_result_methods[] = {
{ "fetch", (PyCFunction) PLy_result_fetch, METH_VARARGS, NULL,},
{ "nrows", (PyCFunction) PLy_result_nrows, METH_VARARGS, NULL },
{ "status", (PyCFunction) PLy_result_status, METH_VARARGS, NULL },
{ NULL, NULL, 0, NULL }
};
static PyMethodDef PLy_methods[] = {
/* logging methods
*/
{ "debug", PLy_debug, METH_VARARGS, NULL },
{ "error", PLy_error, METH_VARARGS, NULL },
{ "fatal", PLy_fatal, METH_VARARGS, NULL },
{ "notice", PLy_notice, METH_VARARGS, NULL },
/* create a stored plan
*/
{ "prepare", PLy_spi_prepare, METH_VARARGS, NULL },
/* execute a plan or query
*/
{ "execute", PLy_spi_execute, METH_VARARGS, NULL },
{ NULL, NULL, 0, NULL }
};
/* plan object methods
*/
PyObject *
PLy_plan_new(void)
{
PLyPlanObject *ob;
enter();
if ((ob = PyObject_NEW(PLyPlanObject, &PLy_PlanType)) == NULL)
return NULL;
ob->plan = NULL;
ob->nargs = 0;
ob->types = NULL;
ob->args = NULL;
return (PyObject *) ob;
}
void
PLy_plan_dealloc(PyObject *arg)
{
PLyPlanObject *ob = (PLyPlanObject *) arg;
enter();
if (ob->plan)
{
/* free the plan...
* pfree(ob->plan);
*
* FIXME -- leaks saved plan on object destruction. can
* this be avoided?
*/
}
if (ob->types)
PLy_free(ob->types);
if (ob->args)
{
int i;
for (i = 0; i < ob->nargs; i++)
PLy_typeinfo_dealloc(&ob->args[i]);
PLy_free(ob->args);
}
PyMem_DEL(arg);
leave();
}
PyObject *
PLy_plan_getattr(PyObject *self, char *name)
{
return Py_FindMethod(PLy_plan_methods, self, name);
}
PyObject *
PLy_plan_status(PyObject *self, PyObject *args)
{
if (PyArg_ParseTuple(args, ""))
{
Py_INCREF(Py_True);
return Py_True;
/* return PyInt_FromLong(self->status); */
}
PyErr_SetString(PLy_exc_error, "plan.status() takes no arguments");
return NULL;
}
/* result object methods
*/
static PyObject *
PLy_result_new(void)
{
PLyResultObject *ob;
enter();
if ((ob = PyObject_NEW(PLyResultObject, &PLy_ResultType)) == NULL)
return NULL;
/* ob->tuples = NULL; */
Py_INCREF(Py_None);
ob->status = Py_None;
ob->nrows = PyInt_FromLong(-1);
ob->rows = PyList_New(0);
return (PyObject *) ob;
}
static void
PLy_result_dealloc(PyObject *arg)
{
PLyResultObject *ob = (PLyResultObject *) arg;
enter();
Py_XDECREF(ob->nrows);
Py_XDECREF(ob->rows);
Py_XDECREF(ob->status);
PyMem_DEL(ob);
}
static PyObject *
PLy_result_getattr(PyObject *self, char *attr)
{
return NULL;
}
static PyObject *
PLy_result_fetch(PyObject *self, PyObject *args)
{
return NULL;
}
static PyObject *
PLy_result_nrows(PyObject *self, PyObject *args)
{
PLyResultObject *ob = (PLyResultObject *) self;
Py_INCREF(ob->nrows);
return ob->nrows;
}
static PyObject *
PLy_result_status(PyObject *self, PyObject *args)
{
PLyResultObject *ob = (PLyResultObject *) self;
Py_INCREF(ob->status);
return ob->status;
}
int
PLy_result_length(PyObject *arg)
{
PLyResultObject *ob = (PLyResultObject *) arg;
return PyList_Size(ob->rows);
}
PyObject *
PLy_result_item(PyObject *arg, int idx)
{
PyObject *rv;
PLyResultObject *ob = (PLyResultObject *) arg;
rv = PyList_GetItem(ob->rows, idx);
if (rv != NULL)
Py_INCREF(rv);
return rv;
}
int
PLy_result_ass_item(PyObject *arg, int idx, PyObject *item)
{
int rv;
PLyResultObject *ob = (PLyResultObject *) arg;
Py_INCREF(item);
rv = PyList_SetItem(ob->rows, idx, item);
return rv;
}
PyObject *
PLy_result_slice(PyObject *arg, int lidx, int hidx)
{
PyObject *rv;
PLyResultObject *ob = (PLyResultObject *) arg;
rv = PyList_GetSlice(ob->rows, lidx, hidx);
if (rv == NULL)
return NULL;
Py_INCREF(rv);
return rv;
}
int
PLy_result_ass_slice(PyObject *arg, int lidx, int hidx, PyObject *slice)
{
int rv;
PLyResultObject *ob = (PLyResultObject *) arg;
rv = PyList_SetSlice(ob->rows, lidx, hidx, slice);
return rv;
}
/* SPI interface
*/
PyObject *
PLy_spi_prepare(PyObject *self, PyObject *args)
{
DECLARE_EXC();
PLyPlanObject *plan;
PyObject *list = NULL;
PyObject *optr = NULL;
char *query;
enter();
if (!PyArg_ParseTuple(args, "s|O", &query, &list))
{
PyErr_SetString(PLy_exc_spi_error,
"Invalid arguments for plpy.prepare()");
return NULL;
}
if ((list) && (!PySequence_Check(list)))
{
PyErr_SetString(PLy_exc_spi_error,
"Second argument in plpy.prepare() must be a sequence");
return NULL;
}
if ((plan = (PLyPlanObject *) PLy_plan_new()) == NULL)
return NULL;
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
Py_DECREF(plan);
Py_XDECREF(optr);
if (!PyErr_Occurred ())
PyErr_SetString(PLy_exc_spi_error,
"Unknown error in PLy_spi_prepare.");
return NULL;
}
if (list != NULL)
{
int nargs, i;
nargs = PySequence_Length(list);
if (nargs > 0)
{
plan->nargs = nargs;
plan->types = PLy_malloc(sizeof(Oid) * nargs);
plan->values = PLy_malloc(sizeof(Datum) * nargs);
plan->args = PLy_malloc(sizeof(PLyTypeInfo) * nargs);
/* the other loop might throw an exception, if PLyTypeInfo
* member isn't properly initialized the Py_DECREF(plan)
* will go boom
*/
for (i = 0; i < nargs; i++)
{
PLy_typeinfo_init(&plan->args[i]);
plan->values[i] = (Datum) NULL;
}
for (i = 0; i < nargs; i++)
{
char *sptr;
HeapTuple typeTup;
Form_pg_type typeStruct;
optr = PySequence_GetItem(list, i);
if (!PyString_Check(optr))
{
PyErr_SetString(PLy_exc_spi_error,
"Type names must be strings.");
RAISE_EXC(1);
}
sptr = PyString_AsString(optr);
typeTup = SearchSysCache(TYPENAME, PointerGetDatum(sptr),
0, 0, 0);
if (!HeapTupleIsValid(typeTup))
{
PLy_exception_set(PLy_exc_spi_error,
"Cache lookup for type `%s' failed.",
sptr);
RAISE_EXC(1);
}
Py_DECREF(optr);
optr = NULL; /* this is important */
plan->types[i] = typeTup->t_data->t_oid;
typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
if (typeStruct->typrelid == InvalidOid)
PLy_output_datum_func(&plan->args[i], typeStruct);
else
{
PyErr_SetString(PLy_exc_spi_error,
"tuples not handled in plpy.prepare, yet.");
RAISE_EXC(1);
}
ReleaseSysCache(typeTup);
}
}
}
plan->plan = SPI_prepare(query, plan->nargs, plan->types);
if (plan->plan == NULL)
{
PLy_exception_set(PLy_exc_spi_error,
"Unable to prepare plan. SPI_prepare failed -- %s.",
PLy_spi_error_string(SPI_result));
RAISE_EXC(1);
}
plan->plan = SPI_saveplan(plan->plan);
if (plan->plan == NULL)
{
PLy_exception_set(PLy_exc_spi_error,
"Unable to save plan. SPI_saveplan failed -- %s.",
PLy_spi_error_string(SPI_result));
RAISE_EXC(1);
}
RESTORE_EXC();
return (PyObject *) plan;
}
/* execute(query="select * from foo", limit=5)
* execute(plan=plan, values=(foo, bar), limit=5)
*/
PyObject *
PLy_spi_execute(PyObject *self, PyObject *args)
{
char *query;
PyObject *plan;
PyObject *list = NULL;
int limit = 0;
enter();
#if 0
/* there should - hahaha - be an python exception set so just
* return NULL. FIXME -- is this needed?
*/
if (PLy_restart_in_progress)
return NULL;
#endif
if (PyArg_ParseTuple(args, "s|i", &query, &limit))
return PLy_spi_execute_query(query, limit);
PyErr_Clear();
if ((PyArg_ParseTuple(args, "O|Oi", &plan, &list, &limit)) &&
(is_PLyPlanObject(plan)))
{
PyObject *rv = PLy_spi_execute_plan(plan, list, limit);
return rv;
}
PyErr_SetString(PLy_exc_error, "Expected a query or plan.");
return NULL;
}
PyObject *
PLy_spi_execute_plan(PyObject *ob, PyObject *list, int limit)
{
DECLARE_EXC();
int nargs, i, rv;
PLyPlanObject *plan;
enter();
if (list != NULL)
{
if ((!PySequence_Check(list)) || (PyString_Check(list)))
{
char *msg = "plpy.execute() takes a sequence as its second argument";
PyErr_SetString(PLy_exc_spi_error, msg);
return NULL;
}
nargs = PySequence_Length(list);
}
else
nargs = 0;
plan = (PLyPlanObject *) ob;
if (nargs != plan->nargs)
{
char *sv;
PyObject *so = PyObject_Str(list);
sv = PyString_AsString(so);
PLy_exception_set(PLy_exc_spi_error,
"Expected sequence of %d arguments, got %d. %s",
plan->nargs, nargs, sv);
Py_DECREF(so);
return NULL;
}
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
/* cleanup plan->values array
*/
for (i = 0; i < nargs; i++)
{
/* FIXME -- typbyval the proper check?
*/
if ((plan->values[i] != (Datum) NULL) &&
(plan->args[i].out.d.typlen < 0))
{
pfree((void *) plan->values[i]);
plan->values[i] = (Datum) NULL;
}
}
if (!PyErr_Occurred())
PyErr_SetString(PLy_exc_error,
"Unknown error in PLy_spi_execute_plan");
return NULL;
}
if (nargs)
{
for (i = 0; i < nargs; i++)
{
Datum typelem, typlen, dv;
PyObject *elem, *so;
char *sv;
typelem = ObjectIdGetDatum(plan->args[i].out.d.typelem);
typlen = Int32GetDatum(plan->args[i].out.d.typlen);
elem = PySequence_GetItem(list, i);
so = PyObject_Str(elem);
sv = PyString_AsString(so);
dv = CStringGetDatum(sv);
/* FIXME -- if this can elog, we have leak
*/
plan->values[i] = FunctionCall3(&(plan->args[i].out.d.typfunc),
dv, typelem, typlen);
Py_DECREF(so);
Py_DECREF(elem);
}
}
rv = SPI_execp(plan->plan, plan->values, NULL, limit);
RESTORE_EXC();
for (i = 0; i < nargs; i++)
{
/* FIXME -- typbyval the proper check?
*/
if ((plan->values[i] != (Datum) NULL) &&
(plan->args[i].out.d.typlen < 0))
{
pfree((void *) plan->values[i]);
plan->values[i] = (Datum) NULL;
}
}
if (rv < 0)
{
PLy_exception_set(PLy_exc_spi_error,
"Unable to execute plan. SPI_execp failed -- %s",
PLy_spi_error_string(rv));
return NULL;
}
return PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv);
}
PyObject *
PLy_spi_execute_query(char *query, int limit)
{
DECLARE_EXC();
int rv;
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
if ((!PLy_restart_in_progress) && (!PyErr_Occurred()))
PyErr_SetString(PLy_exc_spi_error,
"Unknown error in PLy_spi_execute_query.");
return NULL;
}
rv = SPI_exec(query, limit);
RESTORE_EXC();
if (rv < 0)
{
PLy_exception_set(PLy_exc_spi_error,
"Unable to execute query. SPI_exec failed -- %s",
PLy_spi_error_string(rv));
return NULL;
}
return PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv);
}
PyObject *
PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status)
{
PLyResultObject *result;
enter();
result = (PLyResultObject *) PLy_result_new();
Py_DECREF(result->status);
result->status = PyInt_FromLong(status);
if (status == SPI_OK_UTILITY)
{
Py_DECREF(result->nrows);
result->nrows = PyInt_FromLong(0);
}
else if (status != SPI_OK_SELECT)
{
Py_DECREF(result->nrows);
result->nrows = PyInt_FromLong(rows);
}
else
{
DECLARE_EXC();
PLyTypeInfo args;
int i;
PLy_typeinfo_init(&args);
Py_DECREF(result->nrows);
result->nrows = PyInt_FromLong(rows);
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
if (!PyErr_Occurred())
PyErr_SetString(PLy_exc_error,
"Unknown error in PLy_spi_execute_fetch_result");
Py_DECREF(result);
PLy_typeinfo_dealloc(&args);
return NULL;
}
if (rows)
{
Py_DECREF(result->rows);
result->rows = PyList_New(rows);
PLy_input_tuple_funcs(&args, tuptable->tupdesc);
for (i = 0; i < rows; i++)
{
PyObject *row = PLyDict_FromTuple(&args, tuptable->vals[i],
tuptable->tupdesc);
PyList_SetItem(result->rows, i, row);
}
PLy_typeinfo_dealloc(&args);
}
RESTORE_EXC();
}
return (PyObject *) result;
}
const char *
PLy_spi_error_string(int code)
{
switch (code)
{
case SPI_ERROR_TYPUNKNOWN:
return "SPI_ERROR_TYPUNKNOWN";
case SPI_ERROR_NOOUTFUNC:
return "SPI_ERROR_NOOUTFUNC";
case SPI_ERROR_NOATTRIBUTE:
return "SPI_ERROR_NOATTRIBUTE";
case SPI_ERROR_TRANSACTION:
return "SPI_ERROR_TRANSACTION";
case SPI_ERROR_PARAM:
return "SPI_ERROR_PARAM";
case SPI_ERROR_ARGUMENT:
return "SPI_ERROR_ARGUMENT";
case SPI_ERROR_CURSOR:
return "SPI_ERROR_CURSOR";
case SPI_ERROR_UNCONNECTED:
return "SPI_ERROR_UNCONNECTED";
case SPI_ERROR_OPUNKNOWN:
return "SPI_ERROR_OPUNKNOWN";
case SPI_ERROR_COPY:
return "SPI_ERROR_COPY";
case SPI_ERROR_CONNECT:
return "SPI_ERROR_CONNECT";
}
return "Unknown or Invalid code";
}
/* language handler and interpreter initialization
*/
void PLy_init_all(void)
{
static volatile int init_active = 0;
enter();
if (init_active)
elog(FATAL, "plpython: Initialization of language module failed.");
init_active = 1;
Py_Initialize();
PLy_init_interp();
PLy_init_plpy();
PLy_init_safe_interp();
if (PyErr_Occurred())
PLy_elog(FATAL, "Untrapped error in initialization.");
PLy_procedure_cache = PyDict_New();
if (PLy_procedure_cache == NULL)
PLy_elog(ERROR, "Unable to create procedure cache.");
PLy_first_call = 0;
leave();
}
void
PLy_init_interp(void)
{
PyObject *mainmod;
enter();
mainmod = PyImport_AddModule("__main__");
if ((mainmod == NULL) || (PyErr_Occurred()))
PLy_elog(ERROR, "Unable to import '__main__' module.");
Py_INCREF(mainmod);
PLy_interp_globals = PyModule_GetDict(mainmod);
Py_DECREF(mainmod);
if ((PLy_interp_globals == NULL) || (PyErr_Occurred()))
PLy_elog(ERROR, "Unable to initialize globals.");
}
void
PLy_init_plpy(void)
{
PyObject *main_mod, *main_dict, *plpy_mod;
PyObject *plpy, *plpy_dict;
enter();
/* initialize plpy module
*/
plpy = Py_InitModule("plpy", PLy_methods);
plpy_dict = PyModule_GetDict(plpy);
//PyDict_SetItemString(plpy, "PlanType", (PyObject *) &PLy_PlanType);
PLy_exc_error = PyErr_NewException("plpy.Error", NULL, NULL);
PLy_exc_fatal = PyErr_NewException("plpy.Fatal", NULL, NULL);
PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", NULL, NULL);
PyDict_SetItemString(plpy_dict, "Error", PLy_exc_error);
PyDict_SetItemString(plpy_dict, "Fatal", PLy_exc_fatal);
PyDict_SetItemString(plpy_dict, "SPIError", PLy_exc_spi_error);
/* initialize main module, and add plpy
*/
main_mod = PyImport_AddModule("__main__");
main_dict = PyModule_GetDict(main_mod);
plpy_mod = PyImport_AddModule("plpy");
PyDict_SetItemString(main_dict, "plpy", plpy_mod);
if (PyErr_Occurred ())
elog(ERROR, "Unable to init plpy.");
}
void
PLy_init_safe_interp(void)
{
PyObject *rmod;
char *rname = "rexec";
int i, imax;
enter();
rmod = PyImport_ImportModuleEx(rname, PLy_interp_globals,
PLy_interp_globals, Py_None);
if ((rmod == NULL) || (PyErr_Occurred ()))
PLy_elog(ERROR, "Unable to import %s.", rname);
PyDict_SetItemString(PLy_interp_globals, rname, rmod);
PLy_interp_safe = rmod;
imax = sizeof(PLy_importable_modules_list) / sizeof(char *);
PLy_importable_modules = PyTuple_New(imax);
for (i = 0; i < imax; i++)
{
PyObject *m = PyString_FromString(PLy_importable_modules_list[i]);
PyTuple_SetItem(PLy_importable_modules, i, m);
}
PLy_interp_safe_globals = PyDict_New();
if (PLy_interp_safe_globals == NULL)
PLy_elog(ERROR, "Unable to create shared global dictionary.");
}
/* the python interface to the elog function
* don't confuse these with PLy_elog
*/
static PyObject *PLy_log(int, PyObject *, PyObject *);
PyObject *
PLy_debug(PyObject *self, PyObject *args)
{
return PLy_log(DEBUG, self, args);
}
PyObject *
PLy_error(PyObject *self, PyObject *args)
{
return PLy_log(ERROR, self, args);
}
PyObject *
PLy_fatal(PyObject *self, PyObject *args)
{
return PLy_log(FATAL, self, args);
}
PyObject *
PLy_notice(PyObject *self, PyObject *args)
{
return PLy_log(NOTICE, self, args);
}
PyObject *
PLy_log(int level, PyObject *self, PyObject *args)
{
DECLARE_EXC();
PyObject *so;
char *sv;
enter();
if (args == NULL)
elog(NOTICE, "plpython, args is NULL in %s", __FUNCTION__);
so = PyObject_Str(args);
if ((so == NULL) || ((sv = PyString_AsString(so)) == NULL))
{
level = ERROR;
sv = "Unable to parse error message in `plpy.elog'";
}
/* returning NULL here causes the python interpreter to bail.
* when control passes back into plpython_*_handler, we
* check for python exceptions and do the actual elog
* call. actually PLy_elog.
*/
if (level == ERROR)
{
PyErr_SetString(PLy_exc_error, sv);
return NULL;
}
else if (level >= FATAL)
{
PyErr_SetString(PLy_exc_fatal, sv);
return NULL;
}
/* ok, this is a NOTICE, or DEBUG message
*
* but just in case DON'T long jump out of the interpreter!
*/
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
Py_XDECREF(so);
/* the real error message should already be written into
* the postgresql log, no? whatever, this shouldn't happen
* so die hideously.
*/
elog(FATAL, "plpython: Aiieee, elog threw an unknown exception!");
return NULL;
}
elog(level, sv);
RESTORE_EXC();
Py_XDECREF(so);
Py_INCREF(Py_None);
/* return a legal object so the interpreter will continue on its
* merry way
*/
return Py_None;
}
/* output a python traceback/exception via the postgresql elog
* function. not pretty.
*/
static char *PLy_traceback(int *);
static char *PLy_vprintf(const char *fmt, va_list ap);
static char *PLy_printf(const char *fmt, ...);
void
PLy_exception_set(PyObject *exc, const char *fmt, ...)
{
char buf[1024];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
PyErr_SetString(exc, buf);
}
void
PLy_elog(int elevel, const char *fmt,...)
{
DECLARE_EXC();
va_list ap;
char *xmsg, *emsg;
int xlevel;
enter();
xmsg = PLy_traceback(&xlevel);
va_start(ap, fmt);
emsg = PLy_vprintf(fmt, ap);
va_end(ap);
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
mark();
/* elog called siglongjmp. cleanup, restore and reraise
*/
PLy_restart_in_progress += 1;
PLy_free(emsg);
PLy_free(xmsg);
RERAISE_EXC();
}
if (xmsg)
{
elog(elevel, "plpython: %s\n%s", emsg, xmsg);
PLy_free(xmsg);
}
else
elog(elevel, "plpython: %s", emsg);
PLy_free(emsg);
leave();
RESTORE_EXC();
}
char *
PLy_traceback(int *xlevel)
{
PyObject *e, *v, *tb;
PyObject *eob, *vob = NULL;
char *vstr, *estr, *xstr = NULL;
enter();
/* get the current exception
*/
PyErr_Fetch(&e, &v, &tb);
/* oops, no exception, return
*/
if (e == NULL)
{
*xlevel = NOTICE;
return NULL;
}
PyErr_NormalizeException(&e, &v, &tb);
eob = PyObject_Str(e);
if ((v) && ((vob = PyObject_Str(v)) != NULL))
vstr = PyString_AsString(vob);
else
vstr = "Unknown";
estr = PyString_AsString(eob);
xstr = PLy_printf("%s: %s", estr, vstr);
Py_DECREF(eob);
Py_XDECREF(vob);
/* intuit an appropriate error level for based on the exception type
*/
if ((PLy_exc_error) && (PyErr_GivenExceptionMatches(e, PLy_exc_error)))
*xlevel = ERROR;
else if ((PLy_exc_fatal) && (PyErr_GivenExceptionMatches(e, PLy_exc_fatal)))
*xlevel = FATAL;
else
*xlevel = ERROR;
leave();
return xstr;
}
char *
PLy_printf(const char *fmt, ...)
{
va_list ap;
char *emsg;
va_start(ap, fmt);
emsg = PLy_vprintf(fmt, ap);
va_end(ap);
return emsg;
}
char *
PLy_vprintf(const char *fmt, va_list ap)
{
size_t blen;
int bchar, tries = 2;
char *buf;
blen = strlen(fmt) * 2;
if (blen < 256)
blen = 256;
buf = PLy_malloc(blen * sizeof(char));
while (1)
{
bchar = vsnprintf(buf, blen, fmt, ap);
if ((bchar > 0) && (bchar < blen))
return buf;
if (tries-- <= 0)
break;
if (blen > 0)
blen = bchar + 1;
else
blen *= 2;
buf = PLy_realloc(buf, blen);
}
PLy_free(buf);
return NULL;
}
/* python module code
*/
/* some dumb utility functions
*/
void *
PLy_malloc(size_t bytes)
{
void *ptr = malloc(bytes);
if (ptr == NULL)
elog(FATAL, "plpython: Memory exhausted.");
return ptr;
}
void *
PLy_realloc(void *optr, size_t bytes)
{
void *nptr = realloc(optr, bytes);
if (nptr == NULL)
elog(FATAL, "plpython: Memory exhausted.");
return nptr;
}
/* define this away
*/
void
PLy_free(void *ptr)
{
free(ptr);
}
#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
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';
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 ;
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() ;
-- 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');
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';
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') ;
\ No newline at end of file
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) ;
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';
-- 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
--
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)
#!/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...
#!/bin/sh
cd /usr/local/lib/postgresql/langs
cp /home/andrew/projects/pg/plpython/plpython.so ./
cd /home/andrew/projects/pg/plpython
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment