1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-27 12:41:57 +03:00

Allow PL/python to return composite types and result sets

Sven Suursoho
This commit is contained in:
Bruce Momjian
2006-09-02 12:30:01 +00:00
parent b1620c538d
commit 819f22a302
8 changed files with 1359 additions and 99 deletions

View File

@ -55,27 +55,27 @@ except Exception, ex:
return "failed, that wasn''t supposed to happen"
return "succeeded, as expected"'
LANGUAGE plpythonu;
CREATE FUNCTION import_test_one(text) RETURNS text
CREATE FUNCTION import_test_one(p text) RETURNS text
AS
'import sha
digest = sha.new(args[0])
digest = sha.new(p)
return digest.hexdigest()'
LANGUAGE plpythonu;
CREATE FUNCTION import_test_two(users) RETURNS text
CREATE FUNCTION import_test_two(u users) RETURNS text
AS
'import sha
plain = args[0]["fname"] + args[0]["lname"]
plain = u["fname"] + u["lname"]
digest = sha.new(plain);
return "sha hash of " + plain + " is " + digest.hexdigest()'
LANGUAGE plpythonu;
CREATE FUNCTION argument_test_one(users, text, text) RETURNS text
CREATE FUNCTION argument_test_one(u users, a1 text, a2 text) RETURNS text
AS
'keys = args[0].keys()
'keys = u.keys()
keys.sort()
out = []
for key in keys:
out.append("%s: %s" % (key, args[0][key]))
words = args[1] + " " + args[2] + " => {" + ", ".join(out) + "}"
out.append("%s: %s" % (key, u[key]))
words = a1 + " " + a2 + " => {" + ", ".join(out) + "}"
return words'
LANGUAGE plpythonu;
-- these triggers are dedicated to HPHC of RI who
@ -174,40 +174,40 @@ DROP TRIGGER show_trigger_data_trig on trigger_test;
DROP FUNCTION trigger_data();
-- nested calls
--
CREATE FUNCTION nested_call_one(text) RETURNS text
CREATE FUNCTION nested_call_one(a text) RETURNS text
AS
'q = "SELECT nested_call_two(''%s'')" % args[0]
'q = "SELECT nested_call_two(''%s'')" % a
r = plpy.execute(q)
return r[0]'
LANGUAGE plpythonu ;
CREATE FUNCTION nested_call_two(text) RETURNS text
CREATE FUNCTION nested_call_two(a text) RETURNS text
AS
'q = "SELECT nested_call_three(''%s'')" % args[0]
'q = "SELECT nested_call_three(''%s'')" % a
r = plpy.execute(q)
return r[0]'
LANGUAGE plpythonu ;
CREATE FUNCTION nested_call_three(text) RETURNS text
CREATE FUNCTION nested_call_three(a text) RETURNS text
AS
'return args[0]'
'return a'
LANGUAGE plpythonu ;
-- some spi stuff
CREATE FUNCTION spi_prepared_plan_test_one(text) RETURNS text
CREATE FUNCTION spi_prepared_plan_test_one(a 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"
rv = plpy.execute(SD["myplan"], [a])
return "there are " + str(rv[0]["count"]) + " " + str(a) + "s"
except Exception, ex:
plpy.error(str(ex))
return None
'
LANGUAGE plpythonu;
CREATE FUNCTION spi_prepared_plan_test_nested(text) RETURNS text
CREATE FUNCTION spi_prepared_plan_test_nested(a text) RETURNS text
AS
'if not SD.has_key("myplan"):
q = "SELECT spi_prepared_plan_test_one(''%s'') as count" % args[0]
q = "SELECT spi_prepared_plan_test_one(''%s'') as count" % a
SD["myplan"] = plpy.prepare(q)
try:
rv = plpy.execute(SD["myplan"])
@ -223,12 +223,12 @@ return None
CREATE FUNCTION stupid() RETURNS text AS 'return "zarkon"' LANGUAGE plpythonu;
/* a typo
*/
CREATE FUNCTION invalid_type_uncaught(text) RETURNS text
CREATE FUNCTION invalid_type_uncaught(a 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] ])
rv = plpy.execute(SD["plan"], [ a ])
if len(rv):
return rv[0]["fname"]
return None
@ -237,7 +237,7 @@ return None
/* for what it's worth catch the exception generated by
* the typo, and return None
*/
CREATE FUNCTION invalid_type_caught(text) RETURNS text
CREATE FUNCTION invalid_type_caught(a text) RETURNS text
AS
'if not SD.has_key("plan"):
q = "SELECT fname FROM users WHERE lname = $1"
@ -246,7 +246,7 @@ CREATE FUNCTION invalid_type_caught(text) RETURNS text
except plpy.SPIError, ex:
plpy.notice(str(ex))
return None
rv = plpy.execute(SD["plan"], [ args[0] ])
rv = plpy.execute(SD["plan"], [ a ])
if len(rv):
return rv[0]["fname"]
return None
@ -255,7 +255,7 @@ return None
/* 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
CREATE FUNCTION invalid_type_reraised(a text) RETURNS text
AS
'if not SD.has_key("plan"):
q = "SELECT fname FROM users WHERE lname = $1"
@ -263,7 +263,7 @@ CREATE FUNCTION invalid_type_reraised(text) RETURNS text
SD["plan"] = plpy.prepare(q, [ "test" ])
except plpy.SPIError, ex:
plpy.error(str(ex))
rv = plpy.execute(SD["plan"], [ args[0] ])
rv = plpy.execute(SD["plan"], [ a ])
if len(rv):
return rv[0]["fname"]
return None
@ -271,11 +271,11 @@ return None
LANGUAGE plpythonu;
/* no typo no messing about
*/
CREATE FUNCTION valid_type(text) RETURNS text
CREATE FUNCTION valid_type(a 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] ])
rv = plpy.execute(SD["plan"], [ a ])
if len(rv):
return rv[0]["fname"]
return None
@ -300,13 +300,13 @@ CREATE FUNCTION exception_index_invalid_nested() RETURNS text
'rv = plpy.execute("SELECT test5(''foo'')")
return rv[0]'
LANGUAGE plpythonu;
CREATE FUNCTION join_sequences(sequences) RETURNS text
CREATE FUNCTION join_sequences(s sequences) RETURNS text
AS
'if not args[0]["multipart"]:
return args[0]["sequence"]
q = "SELECT sequence FROM xsequences WHERE pid = ''%s''" % args[0]["pid"]
'if not s["multipart"]:
return s["sequence"]
q = "SELECT sequence FROM xsequences WHERE pid = ''%s''" % s["pid"]
rv = plpy.execute(q)
seq = args[0]["sequence"]
seq = s["sequence"]
for r in rv:
seq = seq + r["sequence"]
return seq
@ -357,3 +357,83 @@ $$ LANGUAGE plpythonu;
CREATE FUNCTION test_return_none() RETURNS int AS $$
None
$$ LANGUAGE plpythonu;
--
-- Test named parameters
--
CREATE FUNCTION test_param_names1(a0 integer, a1 text) RETURNS boolean AS $$
assert a0 == args[0]
assert a1 == args[1]
return True
$$ LANGUAGE plpythonu;
CREATE FUNCTION test_param_names2(u users) RETURNS text AS $$
assert u == args[0]
return str(u)
$$ LANGUAGE plpythonu;
-- use deliberately wrong parameter names
CREATE FUNCTION test_param_names3(a0 integer) RETURNS boolean AS $$
try:
assert a1 == args[0]
return False
except NameError, e:
assert e.args[0].find("a1") > -1
return True
$$ LANGUAGE plpythonu;
--
-- Test returning SETOF
--
CREATE FUNCTION test_setof_as_list(count integer, content text) RETURNS SETOF text AS $$
return [ content ]*count
$$ LANGUAGE plpythonu;
CREATE FUNCTION test_setof_as_tuple(count integer, content text) RETURNS SETOF text AS $$
t = ()
for i in xrange(count):
t += ( content, )
return t
$$ LANGUAGE plpythonu;
CREATE FUNCTION test_setof_as_iterator(count integer, content text) RETURNS SETOF text AS $$
class producer:
def __init__ (self, icount, icontent):
self.icontent = icontent
self.icount = icount
def __iter__ (self):
return self
def next (self):
if self.icount == 0:
raise StopIteration
self.icount -= 1
return self.icontent
return producer(count, content)
$$ LANGUAGE plpythonu;
--
-- Test returning tuples
--
CREATE FUNCTION test_table_record_as(typ text, first text, second integer, retnull boolean) RETURNS table_record AS $$
if retnull:
return None
if typ == 'dict':
return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' }
elif typ == 'tuple':
return ( first, second )
elif typ == 'list':
return [ first, second ]
elif typ == 'obj':
class type_record: pass
type_record.first = first
type_record.second = second
return type_record
$$ LANGUAGE plpythonu;
CREATE FUNCTION test_type_record_as(typ text, first text, second integer, retnull boolean) RETURNS type_record AS $$
if retnull:
return None
if typ == 'dict':
return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' }
elif typ == 'tuple':
return ( first, second )
elif typ == 'list':
return [ first, second ]
elif typ == 'obj':
class type_record: pass
type_record.first = first
type_record.second = second
return type_record
$$ LANGUAGE plpythonu;

View File

@ -44,3 +44,11 @@ CREATE INDEX xsequences_pid_idx ON xsequences(pid) ;
CREATE TABLE unicode_test (
testvalue text NOT NULL
);
CREATE TABLE table_record (
first text,
second int4
) ;
CREATE TYPE type_record AS (
first text,
second int4
) ;

View File

@ -198,3 +198,344 @@ SELECT test_return_none(), test_return_none() IS NULL AS "is null";
| t
(1 row)
-- Test for functions with named parameters
SELECT test_param_names1(1,'text');
test_param_names1
-------------------
t
(1 row)
SELECT test_param_names2(users) from users;
test_param_names2
----------------------------------------------------------------------------
{'lname': 'doe', 'username': 'j_doe', 'userid': 1, 'fname': 'jane'}
{'lname': 'doe', 'username': 'johnd', 'userid': 2, 'fname': 'john'}
{'lname': 'doe', 'username': 'w_doe', 'userid': 3, 'fname': 'willem'}
{'lname': 'smith', 'username': 'slash', 'userid': 4, 'fname': 'rick'}
{'lname': 'smith', 'username': 'w_smith', 'userid': 5, 'fname': 'willem'}
{'lname': 'darwin', 'username': 'beagle', 'userid': 6, 'fname': 'charles'}
(6 rows)
SELECT test_param_names3(1);
test_param_names3
-------------------
t
(1 row)
-- Test set returning functions
SELECT test_setof_as_list(0, 'list');
test_setof_as_list
--------------------
(0 rows)
SELECT test_setof_as_list(1, 'list');
test_setof_as_list
--------------------
list
(1 row)
SELECT test_setof_as_list(2, 'list');
test_setof_as_list
--------------------
list
list
(2 rows)
SELECT test_setof_as_list(2, null);
test_setof_as_list
--------------------
(2 rows)
SELECT test_setof_as_tuple(0, 'tuple');
test_setof_as_tuple
---------------------
(0 rows)
SELECT test_setof_as_tuple(1, 'tuple');
test_setof_as_tuple
---------------------
tuple
(1 row)
SELECT test_setof_as_tuple(2, 'tuple');
test_setof_as_tuple
---------------------
tuple
tuple
(2 rows)
SELECT test_setof_as_tuple(2, null);
test_setof_as_tuple
---------------------
(2 rows)
SELECT test_setof_as_iterator(0, 'list');
test_setof_as_iterator
------------------------
(0 rows)
SELECT test_setof_as_iterator(1, 'list');
test_setof_as_iterator
------------------------
list
(1 row)
SELECT test_setof_as_iterator(2, 'list');
test_setof_as_iterator
------------------------
list
list
(2 rows)
SELECT test_setof_as_iterator(2, null);
test_setof_as_iterator
------------------------
(2 rows)
-- Test tuple returning functions
SELECT * FROM test_table_record_as('dict', null, null, false);
first | second
-------+--------
|
(1 row)
SELECT * FROM test_table_record_as('dict', 'one', null, false);
first | second
-------+--------
one |
(1 row)
SELECT * FROM test_table_record_as('dict', null, 2, false);
first | second
-------+--------
| 2
(1 row)
SELECT * FROM test_table_record_as('dict', 'three', 3, false);
first | second
-------+--------
three | 3
(1 row)
SELECT * FROM test_table_record_as('dict', null, null, true);
first | second
-------+--------
|
(1 row)
SELECT * FROM test_table_record_as('tuple', null, null, false);
first | second
-------+--------
|
(1 row)
SELECT * FROM test_table_record_as('tuple', 'one', null, false);
first | second
-------+--------
one |
(1 row)
SELECT * FROM test_table_record_as('tuple', null, 2, false);
first | second
-------+--------
| 2
(1 row)
SELECT * FROM test_table_record_as('tuple', 'three', 3, false);
first | second
-------+--------
three | 3
(1 row)
SELECT * FROM test_table_record_as('tuple', null, null, true);
first | second
-------+--------
|
(1 row)
SELECT * FROM test_table_record_as('list', null, null, false);
first | second
-------+--------
|
(1 row)
SELECT * FROM test_table_record_as('list', 'one', null, false);
first | second
-------+--------
one |
(1 row)
SELECT * FROM test_table_record_as('list', null, 2, false);
first | second
-------+--------
| 2
(1 row)
SELECT * FROM test_table_record_as('list', 'three', 3, false);
first | second
-------+--------
three | 3
(1 row)
SELECT * FROM test_table_record_as('list', null, null, true);
first | second
-------+--------
|
(1 row)
SELECT * FROM test_table_record_as('obj', null, null, false);
first | second
-------+--------
|
(1 row)
SELECT * FROM test_table_record_as('obj', 'one', null, false);
first | second
-------+--------
one |
(1 row)
SELECT * FROM test_table_record_as('obj', null, 2, false);
first | second
-------+--------
| 2
(1 row)
SELECT * FROM test_table_record_as('obj', 'three', 3, false);
first | second
-------+--------
three | 3
(1 row)
SELECT * FROM test_table_record_as('obj', null, null, true);
first | second
-------+--------
|
(1 row)
SELECT * FROM test_type_record_as('dict', null, null, false);
first | second
-------+--------
|
(1 row)
SELECT * FROM test_type_record_as('dict', 'one', null, false);
first | second
-------+--------
one |
(1 row)
SELECT * FROM test_type_record_as('dict', null, 2, false);
first | second
-------+--------
| 2
(1 row)
SELECT * FROM test_type_record_as('dict', 'three', 3, false);
first | second
-------+--------
three | 3
(1 row)
SELECT * FROM test_type_record_as('dict', null, null, true);
first | second
-------+--------
|
(1 row)
SELECT * FROM test_type_record_as('tuple', null, null, false);
first | second
-------+--------
|
(1 row)
SELECT * FROM test_type_record_as('tuple', 'one', null, false);
first | second
-------+--------
one |
(1 row)
SELECT * FROM test_type_record_as('tuple', null, 2, false);
first | second
-------+--------
| 2
(1 row)
SELECT * FROM test_type_record_as('tuple', 'three', 3, false);
first | second
-------+--------
three | 3
(1 row)
SELECT * FROM test_type_record_as('tuple', null, null, true);
first | second
-------+--------
|
(1 row)
SELECT * FROM test_type_record_as('list', null, null, false);
first | second
-------+--------
|
(1 row)
SELECT * FROM test_type_record_as('list', 'one', null, false);
first | second
-------+--------
one |
(1 row)
SELECT * FROM test_type_record_as('list', null, 2, false);
first | second
-------+--------
| 2
(1 row)
SELECT * FROM test_type_record_as('list', 'three', 3, false);
first | second
-------+--------
three | 3
(1 row)
SELECT * FROM test_type_record_as('list', null, null, true);
first | second
-------+--------
|
(1 row)
SELECT * FROM test_type_record_as('obj', null, null, false);
first | second
-------+--------
|
(1 row)
SELECT * FROM test_type_record_as('obj', 'one', null, false);
first | second
-------+--------
one |
(1 row)
SELECT * FROM test_type_record_as('obj', null, 2, false);
first | second
-------+--------
| 2
(1 row)
SELECT * FROM test_type_record_as('obj', 'three', 3, false);
first | second
-------+--------
three | 3
(1 row)
SELECT * FROM test_type_record_as('obj', null, null, true);
first | second
-------+--------
|
(1 row)

View File

@ -1,7 +1,7 @@
/**********************************************************************
* plpython.c - python as a procedural language for PostgreSQL
*
* $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.86 2006/08/27 23:47:58 tgl Exp $
* $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.87 2006/09/02 12:30:01 momjian Exp $
*
*********************************************************************
*/
@ -30,6 +30,7 @@
#include "catalog/pg_type.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "funcapi.h"
#include "fmgr.h"
#include "nodes/makefuncs.h"
#include "parser/parse_type.h"
@ -121,6 +122,9 @@ typedef struct PLyProcedure
bool fn_readonly;
PLyTypeInfo result; /* also used to store info for trigger tuple
* type */
bool is_setof; /* true, if procedure returns result set */
PyObject *setof; /* contents of result set. */
char **argnames; /* Argument names */
PLyTypeInfo args[FUNC_MAX_ARGS];
int nargs;
PyObject *code; /* compiled procedure code */
@ -196,6 +200,7 @@ static Datum PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure *);
static HeapTuple PLy_trigger_handler(FunctionCallInfo fcinfo, PLyProcedure *);
static PyObject *PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *);
static void PLy_function_delete_args(PLyProcedure *);
static PyObject *PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *,
HeapTuple *);
static HeapTuple PLy_modify_tuple(PLyProcedure *, PyObject *,
@ -231,6 +236,9 @@ static PyObject *PLyInt_FromString(const char *);
static PyObject *PLyLong_FromString(const char *);
static PyObject *PLyString_FromString(const char *);
static HeapTuple PLyMapping_ToTuple(PLyTypeInfo *, PyObject *);
static HeapTuple PLySequence_ToTuple(PLyTypeInfo *, PyObject *);
static HeapTuple PLyObject_ToTuple(PLyTypeInfo *, PyObject *);
/*
* Currently active plpython function
@ -748,11 +756,17 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc)
PG_TRY();
{
plargs = PLy_function_build_args(fcinfo, proc);
plrv = PLy_procedure_call(proc, "args", plargs);
Assert(plrv != NULL);
Assert(!PLy_error_in_progress);
if (!proc->is_setof || proc->setof == NULL)
{
/* Simple type returning function or first time for SETOF function */
plargs = PLy_function_build_args(fcinfo, proc);
plrv = PLy_procedure_call(proc, "args", plargs);
if (!proc->is_setof)
/* SETOF function parameters will be deleted when last row is returned */
PLy_function_delete_args(proc);
Assert(plrv != NULL);
Assert(!PLy_error_in_progress);
}
/*
* Disconnect from SPI manager and then create the return values datum
@ -763,6 +777,67 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc)
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
if (proc->is_setof)
{
bool has_error = false;
ReturnSetInfo *rsi = (ReturnSetInfo *)fcinfo->resultinfo;
if (proc->setof == NULL)
{
/* first time -- do checks and setup */
if (!rsi || !IsA(rsi, ReturnSetInfo) ||
(rsi->allowedModes & SFRM_ValuePerCall) == 0)
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("only value per call is allowed")));
}
rsi->returnMode = SFRM_ValuePerCall;
/* Make iterator out of returned object */
proc->setof = PyObject_GetIter(plrv);
Py_DECREF(plrv);
plrv = NULL;
if (proc->setof == NULL)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("returned object can not be iterated"),
errdetail("SETOF must be returned as iterable object")));
}
/* Fetch next from iterator */
plrv = PyIter_Next(proc->setof);
if (plrv)
rsi->isDone = ExprMultipleResult;
else
{
rsi->isDone = ExprEndResult;
has_error = PyErr_Occurred() != NULL;
}
if (rsi->isDone == ExprEndResult)
{
/* Iterator is exhausted or error happened */
Py_DECREF(proc->setof);
proc->setof = NULL;
Py_XDECREF(plargs);
Py_XDECREF(plrv);
Py_XDECREF(plrv_so);
PLy_function_delete_args(proc);
if (has_error)
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION),
errmsg("error fetching next item from iterator")));
fcinfo->isnull = true;
return (Datum)NULL;
}
}
/*
* If the function is declared to return void, the Python
* return value must be None. For void-returning functions, we
@ -784,10 +859,39 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc)
else if (plrv == Py_None)
{
fcinfo->isnull = true;
rv = InputFunctionCall(&proc->result.out.d.typfunc,
NULL,
proc->result.out.d.typioparam,
-1);
if (proc->result.is_rowtype < 1)
rv = InputFunctionCall(&proc->result.out.d.typfunc,
NULL,
proc->result.out.d.typioparam,
-1);
else
/* Tuple as None */
rv = (Datum) NULL;
}
else if (proc->result.is_rowtype >= 1)
{
HeapTuple tuple = NULL;
if (PySequence_Check(plrv))
/* composite type as sequence (tuple, list etc) */
tuple = PLySequence_ToTuple(&proc->result, plrv);
else if (PyMapping_Check(plrv))
/* composite type as mapping (currently only dict) */
tuple = PLyMapping_ToTuple(&proc->result, plrv);
else
/* returned as smth, must provide method __getattr__(name) */
tuple = PLyObject_ToTuple(&proc->result, plrv);
if (tuple != NULL)
{
fcinfo->isnull = false;
rv = HeapTupleGetDatum(tuple);
}
else
{
fcinfo->isnull = true;
rv = (Datum) NULL;
}
}
else
{
@ -912,10 +1016,10 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc)
arg = Py_None;
}
/*
* FIXME -- error check this
*/
PyList_SetItem(args, i, arg);
if (PyList_SetItem(args, i, arg) == -1 ||
(proc->argnames &&
PyDict_SetItemString(proc->globals, proc->argnames[i], arg) == -1))
PLy_elog(ERROR, "problem setting up arguments for \"%s\"", proc->proname);
arg = NULL;
}
}
@ -932,6 +1036,19 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc)
}
static void
PLy_function_delete_args(PLyProcedure *proc)
{
int i;
if (!proc->argnames)
return;
for (i = 0; i < proc->nargs; i++)
PyDict_DelItemString(proc->globals, proc->argnames[i]);
}
/*
* PLyProcedure functions
*/
@ -1002,6 +1119,9 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid,
bool isnull;
int i,
rv;
Datum argnames;
Datum *elems;
int nelems;
procStruct = (Form_pg_proc) GETSTRUCT(procTup);
@ -1033,6 +1153,9 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid,
proc->nargs = 0;
proc->code = proc->statics = NULL;
proc->globals = proc->me = NULL;
proc->is_setof = procStruct->proretset;
proc->setof = NULL;
proc->argnames = NULL;
PG_TRY();
{
@ -1069,9 +1192,11 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid,
}
if (rvTypeStruct->typtype == 'c')
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("plpython functions cannot return tuples yet")));
{
/* Tuple: set up later, during first call to PLy_function_handler */
proc->result.out.d.typoid = procStruct->prorettype;
proc->result.is_rowtype = 2;
}
else
PLy_output_datum_func(&proc->result, rvTypeTup);
@ -1094,6 +1219,20 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid,
* arguments.
*/
proc->nargs = fcinfo->nargs;
if (proc->nargs)
{
argnames = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_proargnames, &isnull);
if (!isnull)
{
deconstruct_array(DatumGetArrayTypeP(argnames), TEXTOID, -1, false, 'i',
&elems, NULL, &nelems);
if (nelems != proc->nargs)
elog(ERROR,
"proargnames must have the same number of elements "
"as the function has arguments");
proc->argnames = (char **) PLy_malloc(sizeof(char *)*proc->nargs);
}
}
for (i = 0; i < fcinfo->nargs; i++)
{
HeapTuple argTypeTup;
@ -1122,8 +1261,11 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid,
proc->args[i].is_rowtype = 2; /* still need to set I/O funcs */
ReleaseSysCache(argTypeTup);
}
/* Fetch argument name */
if (proc->argnames)
proc->argnames[i] = PLy_strdup(DatumGetCString(DirectFunctionCall1(textout, elems[i])));
}
/*
* get the text of the function.
@ -1259,6 +1401,7 @@ PLy_procedure_delete(PLyProcedure * proc)
if (proc->pyname)
PLy_free(proc->pyname);
for (i = 0; i < proc->nargs; i++)
{
if (proc->args[i].is_rowtype == 1)
{
if (proc->args[i].in.r.atts)
@ -1266,6 +1409,11 @@ PLy_procedure_delete(PLyProcedure * proc)
if (proc->args[i].out.r.atts)
PLy_free(proc->args[i].out.r.atts);
}
if (proc->argnames && proc->argnames[i])
PLy_free(proc->argnames[i]);
}
if (proc->argnames)
PLy_free(proc->argnames);
}
/* conversion functions. remember output from python is
@ -1524,6 +1672,247 @@ PLyDict_FromTuple(PLyTypeInfo * info, HeapTuple tuple, TupleDesc desc)
return dict;
}
static HeapTuple
PLyMapping_ToTuple(PLyTypeInfo *info, PyObject *mapping)
{
TupleDesc desc;
HeapTuple tuple;
Datum *values;
char *nulls;
int i;
Assert(PyMapping_Check(mapping));
desc = lookup_rowtype_tupdesc(info->out.d.typoid, -1);
if (info->is_rowtype == 2)
PLy_output_tuple_funcs(info, desc);
Assert(info->is_rowtype == 1);
/* Build tuple */
values = palloc(sizeof(Datum)*desc->natts);
nulls = palloc(sizeof(char)*desc->natts);
for (i = 0; i < desc->natts; ++i)
{
char *key;
PyObject *value,
*so;
key = NameStr(desc->attrs[i]->attname);
value = so = NULL;
PG_TRY();
{
value = PyMapping_GetItemString(mapping, key);
if (value == Py_None)
{
values[i] = (Datum) NULL;
nulls[i] = 'n';
}
else if (value)
{
char *valuestr;
so = PyObject_Str(value);
if (so == NULL)
PLy_elog(ERROR, "can't convert mapping type");
valuestr = PyString_AsString(so);
values[i] = InputFunctionCall(&info->out.r.atts[i].typfunc
, valuestr
, info->out.r.atts[i].typioparam
, -1);
Py_DECREF(so);
so = NULL;
nulls[i] = ' ';
}
else
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("no mapping found with key \"%s\"", key),
errhint("to return null in specific column, "
"add value None to map with key named after column")));
Py_XDECREF(value);
value = NULL;
}
PG_CATCH();
{
Py_XDECREF(so);
Py_XDECREF(value);
PG_RE_THROW();
}
PG_END_TRY();
}
tuple = heap_formtuple(desc, values, nulls);
ReleaseTupleDesc(desc);
pfree(values);
pfree(nulls);
return tuple;
}
static HeapTuple
PLySequence_ToTuple(PLyTypeInfo *info, PyObject *sequence)
{
TupleDesc desc;
HeapTuple tuple;
Datum *values;
char *nulls;
int i;
Assert(PySequence_Check(sequence));
/*
* Check that sequence length is exactly same as PG tuple's. We actually
* can ignore exceeding items or assume missing ones as null but to
* avoid plpython developer's errors we are strict here
*/
desc = lookup_rowtype_tupdesc(info->out.d.typoid, -1);
if (PySequence_Length(sequence) != desc->natts)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("returned sequence's length must be same as tuple's length")));
if (info->is_rowtype == 2)
PLy_output_tuple_funcs(info, desc);
Assert(info->is_rowtype == 1);
/* Build tuple */
values = palloc(sizeof(Datum)*desc->natts);
nulls = palloc(sizeof(char)*desc->natts);
for (i = 0; i < desc->natts; ++i)
{
PyObject *value,
*so;
value = so = NULL;
PG_TRY();
{
value = PySequence_GetItem(sequence, i);
Assert(value);
if (value == Py_None)
{
values[i] = (Datum) NULL;
nulls[i] = 'n';
}
else if (value)
{
char *valuestr;
so = PyObject_Str(value);
if (so == NULL)
PLy_elog(ERROR, "can't convert sequence type");
valuestr = PyString_AsString(so);
values[i] = InputFunctionCall(&info->out.r.atts[i].typfunc
, valuestr
, info->out.r.atts[i].typioparam
, -1);
Py_DECREF(so);
so = NULL;
nulls[i] = ' ';
}
Py_XDECREF(value);
value = NULL;
}
PG_CATCH();
{
Py_XDECREF(so);
Py_XDECREF(value);
PG_RE_THROW();
}
PG_END_TRY();
}
tuple = heap_formtuple(desc, values, nulls);
ReleaseTupleDesc(desc);
pfree(values);
pfree(nulls);
return tuple;
}
static HeapTuple
PLyObject_ToTuple(PLyTypeInfo *info, PyObject *object)
{
TupleDesc desc;
HeapTuple tuple;
Datum *values;
char *nulls;
int i;
desc = lookup_rowtype_tupdesc(info->out.d.typoid, -1);
if (info->is_rowtype == 2)
PLy_output_tuple_funcs(info, desc);
Assert(info->is_rowtype == 1);
/* Build tuple */
values = palloc(sizeof(Datum)*desc->natts);
nulls = palloc(sizeof(char)*desc->natts);
for (i = 0; i < desc->natts; ++i)
{
char *key;
PyObject *value,
*so;
key = NameStr(desc->attrs[i]->attname);
value = so = NULL;
PG_TRY();
{
value = PyObject_GetAttrString(object, key);
if (value == Py_None)
{
values[i] = (Datum) NULL;
nulls[i] = 'n';
}
else if (value)
{
char *valuestr;
so = PyObject_Str(value);
if (so == NULL)
PLy_elog(ERROR, "can't convert object type");
valuestr = PyString_AsString(so);
values[i] = InputFunctionCall(&info->out.r.atts[i].typfunc
, valuestr
, info->out.r.atts[i].typioparam
, -1);
Py_DECREF(so);
so = NULL;
nulls[i] = ' ';
}
else
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("no attribute named \"%s\"", key),
errhint("to return null in specific column, "
"let returned object to have attribute named "
"after column with value None")));
Py_XDECREF(value);
value = NULL;
}
PG_CATCH();
{
Py_XDECREF(so);
Py_XDECREF(value);
PG_RE_THROW();
}
PG_END_TRY();
}
tuple = heap_formtuple(desc, values, nulls);
ReleaseTupleDesc(desc);
pfree(values);
pfree(nulls);
return tuple;
}
/* initialization, some python variables function declared here */
/* interface to postgresql elog */

View File

@ -65,29 +65,29 @@ except Exception, ex:
return "succeeded, as expected"'
LANGUAGE plpythonu;
CREATE FUNCTION import_test_one(text) RETURNS text
CREATE FUNCTION import_test_one(p text) RETURNS text
AS
'import sha
digest = sha.new(args[0])
digest = sha.new(p)
return digest.hexdigest()'
LANGUAGE plpythonu;
CREATE FUNCTION import_test_two(users) RETURNS text
CREATE FUNCTION import_test_two(u users) RETURNS text
AS
'import sha
plain = args[0]["fname"] + args[0]["lname"]
plain = u["fname"] + u["lname"]
digest = sha.new(plain);
return "sha hash of " + plain + " is " + digest.hexdigest()'
LANGUAGE plpythonu;
CREATE FUNCTION argument_test_one(users, text, text) RETURNS text
CREATE FUNCTION argument_test_one(u users, a1 text, a2 text) RETURNS text
AS
'keys = args[0].keys()
'keys = u.keys()
keys.sort()
out = []
for key in keys:
out.append("%s: %s" % (key, args[0][key]))
words = args[1] + " " + args[2] + " => {" + ", ".join(out) + "}"
out.append("%s: %s" % (key, u[key]))
words = a1 + " " + a2 + " => {" + ", ".join(out) + "}"
return words'
LANGUAGE plpythonu;
@ -176,45 +176,45 @@ DROP FUNCTION trigger_data();
-- nested calls
--
CREATE FUNCTION nested_call_one(text) RETURNS text
CREATE FUNCTION nested_call_one(a text) RETURNS text
AS
'q = "SELECT nested_call_two(''%s'')" % args[0]
'q = "SELECT nested_call_two(''%s'')" % a
r = plpy.execute(q)
return r[0]'
LANGUAGE plpythonu ;
CREATE FUNCTION nested_call_two(text) RETURNS text
CREATE FUNCTION nested_call_two(a text) RETURNS text
AS
'q = "SELECT nested_call_three(''%s'')" % args[0]
'q = "SELECT nested_call_three(''%s'')" % a
r = plpy.execute(q)
return r[0]'
LANGUAGE plpythonu ;
CREATE FUNCTION nested_call_three(text) RETURNS text
CREATE FUNCTION nested_call_three(a text) RETURNS text
AS
'return args[0]'
'return a'
LANGUAGE plpythonu ;
-- some spi stuff
CREATE FUNCTION spi_prepared_plan_test_one(text) RETURNS text
CREATE FUNCTION spi_prepared_plan_test_one(a 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"
rv = plpy.execute(SD["myplan"], [a])
return "there are " + str(rv[0]["count"]) + " " + str(a) + "s"
except Exception, ex:
plpy.error(str(ex))
return None
'
LANGUAGE plpythonu;
CREATE FUNCTION spi_prepared_plan_test_nested(text) RETURNS text
CREATE FUNCTION spi_prepared_plan_test_nested(a text) RETURNS text
AS
'if not SD.has_key("myplan"):
q = "SELECT spi_prepared_plan_test_one(''%s'') as count" % args[0]
q = "SELECT spi_prepared_plan_test_one(''%s'') as count" % a
SD["myplan"] = plpy.prepare(q)
try:
rv = plpy.execute(SD["myplan"])
@ -233,12 +233,12 @@ CREATE FUNCTION stupid() RETURNS text AS 'return "zarkon"' LANGUAGE plpythonu;
/* a typo
*/
CREATE FUNCTION invalid_type_uncaught(text) RETURNS text
CREATE FUNCTION invalid_type_uncaught(a 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] ])
rv = plpy.execute(SD["plan"], [ a ])
if len(rv):
return rv[0]["fname"]
return None
@ -248,7 +248,7 @@ return None
/* for what it's worth catch the exception generated by
* the typo, and return None
*/
CREATE FUNCTION invalid_type_caught(text) RETURNS text
CREATE FUNCTION invalid_type_caught(a text) RETURNS text
AS
'if not SD.has_key("plan"):
q = "SELECT fname FROM users WHERE lname = $1"
@ -257,7 +257,7 @@ CREATE FUNCTION invalid_type_caught(text) RETURNS text
except plpy.SPIError, ex:
plpy.notice(str(ex))
return None
rv = plpy.execute(SD["plan"], [ args[0] ])
rv = plpy.execute(SD["plan"], [ a ])
if len(rv):
return rv[0]["fname"]
return None
@ -267,7 +267,7 @@ return None
/* 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
CREATE FUNCTION invalid_type_reraised(a text) RETURNS text
AS
'if not SD.has_key("plan"):
q = "SELECT fname FROM users WHERE lname = $1"
@ -275,7 +275,7 @@ CREATE FUNCTION invalid_type_reraised(text) RETURNS text
SD["plan"] = plpy.prepare(q, [ "test" ])
except plpy.SPIError, ex:
plpy.error(str(ex))
rv = plpy.execute(SD["plan"], [ args[0] ])
rv = plpy.execute(SD["plan"], [ a ])
if len(rv):
return rv[0]["fname"]
return None
@ -285,11 +285,11 @@ return None
/* no typo no messing about
*/
CREATE FUNCTION valid_type(text) RETURNS text
CREATE FUNCTION valid_type(a 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] ])
rv = plpy.execute(SD["plan"], [ a ])
if len(rv):
return rv[0]["fname"]
return None
@ -318,13 +318,13 @@ return rv[0]'
LANGUAGE plpythonu;
CREATE FUNCTION join_sequences(sequences) RETURNS text
CREATE FUNCTION join_sequences(s sequences) RETURNS text
AS
'if not args[0]["multipart"]:
return args[0]["sequence"]
q = "SELECT sequence FROM xsequences WHERE pid = ''%s''" % args[0]["pid"]
'if not s["multipart"]:
return s["sequence"]
q = "SELECT sequence FROM xsequences WHERE pid = ''%s''" % s["pid"]
rv = plpy.execute(q)
seq = args[0]["sequence"]
seq = s["sequence"]
for r in rv:
seq = seq + r["sequence"]
return seq
@ -389,3 +389,95 @@ $$ LANGUAGE plpythonu;
CREATE FUNCTION test_return_none() RETURNS int AS $$
None
$$ LANGUAGE plpythonu;
--
-- Test named parameters
--
CREATE FUNCTION test_param_names1(a0 integer, a1 text) RETURNS boolean AS $$
assert a0 == args[0]
assert a1 == args[1]
return True
$$ LANGUAGE plpythonu;
CREATE FUNCTION test_param_names2(u users) RETURNS text AS $$
assert u == args[0]
return str(u)
$$ LANGUAGE plpythonu;
-- use deliberately wrong parameter names
CREATE FUNCTION test_param_names3(a0 integer) RETURNS boolean AS $$
try:
assert a1 == args[0]
return False
except NameError, e:
assert e.args[0].find("a1") > -1
return True
$$ LANGUAGE plpythonu;
--
-- Test returning SETOF
--
CREATE FUNCTION test_setof_as_list(count integer, content text) RETURNS SETOF text AS $$
return [ content ]*count
$$ LANGUAGE plpythonu;
CREATE FUNCTION test_setof_as_tuple(count integer, content text) RETURNS SETOF text AS $$
t = ()
for i in xrange(count):
t += ( content, )
return t
$$ LANGUAGE plpythonu;
CREATE FUNCTION test_setof_as_iterator(count integer, content text) RETURNS SETOF text AS $$
class producer:
def __init__ (self, icount, icontent):
self.icontent = icontent
self.icount = icount
def __iter__ (self):
return self
def next (self):
if self.icount == 0:
raise StopIteration
self.icount -= 1
return self.icontent
return producer(count, content)
$$ LANGUAGE plpythonu;
--
-- Test returning tuples
--
CREATE FUNCTION test_table_record_as(typ text, first text, second integer, retnull boolean) RETURNS table_record AS $$
if retnull:
return None
if typ == 'dict':
return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' }
elif typ == 'tuple':
return ( first, second )
elif typ == 'list':
return [ first, second ]
elif typ == 'obj':
class type_record: pass
type_record.first = first
type_record.second = second
return type_record
$$ LANGUAGE plpythonu;
CREATE FUNCTION test_type_record_as(typ text, first text, second integer, retnull boolean) RETURNS type_record AS $$
if retnull:
return None
if typ == 'dict':
return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' }
elif typ == 'tuple':
return ( first, second )
elif typ == 'list':
return [ first, second ]
elif typ == 'obj':
class type_record: pass
type_record.first = first
type_record.second = second
return type_record
$$ LANGUAGE plpythonu;

View File

@ -42,3 +42,13 @@ CREATE INDEX xsequences_pid_idx ON xsequences(pid) ;
CREATE TABLE unicode_test (
testvalue text NOT NULL
);
CREATE TABLE table_record (
first text,
second int4
) ;
CREATE TYPE type_record AS (
first text,
second int4
) ;

View File

@ -73,3 +73,73 @@ SELECT newline_crlf();
SELECT test_void_func1(), test_void_func1() IS NULL AS "is null";
SELECT test_void_func2(); -- should fail
SELECT test_return_none(), test_return_none() IS NULL AS "is null";
-- Test for functions with named parameters
SELECT test_param_names1(1,'text');
SELECT test_param_names2(users) from users;
SELECT test_param_names3(1);
-- Test set returning functions
SELECT test_setof_as_list(0, 'list');
SELECT test_setof_as_list(1, 'list');
SELECT test_setof_as_list(2, 'list');
SELECT test_setof_as_list(2, null);
SELECT test_setof_as_tuple(0, 'tuple');
SELECT test_setof_as_tuple(1, 'tuple');
SELECT test_setof_as_tuple(2, 'tuple');
SELECT test_setof_as_tuple(2, null);
SELECT test_setof_as_iterator(0, 'list');
SELECT test_setof_as_iterator(1, 'list');
SELECT test_setof_as_iterator(2, 'list');
SELECT test_setof_as_iterator(2, null);
-- Test tuple returning functions
SELECT * FROM test_table_record_as('dict', null, null, false);
SELECT * FROM test_table_record_as('dict', 'one', null, false);
SELECT * FROM test_table_record_as('dict', null, 2, false);
SELECT * FROM test_table_record_as('dict', 'three', 3, false);
SELECT * FROM test_table_record_as('dict', null, null, true);
SELECT * FROM test_table_record_as('tuple', null, null, false);
SELECT * FROM test_table_record_as('tuple', 'one', null, false);
SELECT * FROM test_table_record_as('tuple', null, 2, false);
SELECT * FROM test_table_record_as('tuple', 'three', 3, false);
SELECT * FROM test_table_record_as('tuple', null, null, true);
SELECT * FROM test_table_record_as('list', null, null, false);
SELECT * FROM test_table_record_as('list', 'one', null, false);
SELECT * FROM test_table_record_as('list', null, 2, false);
SELECT * FROM test_table_record_as('list', 'three', 3, false);
SELECT * FROM test_table_record_as('list', null, null, true);
SELECT * FROM test_table_record_as('obj', null, null, false);
SELECT * FROM test_table_record_as('obj', 'one', null, false);
SELECT * FROM test_table_record_as('obj', null, 2, false);
SELECT * FROM test_table_record_as('obj', 'three', 3, false);
SELECT * FROM test_table_record_as('obj', null, null, true);
SELECT * FROM test_type_record_as('dict', null, null, false);
SELECT * FROM test_type_record_as('dict', 'one', null, false);
SELECT * FROM test_type_record_as('dict', null, 2, false);
SELECT * FROM test_type_record_as('dict', 'three', 3, false);
SELECT * FROM test_type_record_as('dict', null, null, true);
SELECT * FROM test_type_record_as('tuple', null, null, false);
SELECT * FROM test_type_record_as('tuple', 'one', null, false);
SELECT * FROM test_type_record_as('tuple', null, 2, false);
SELECT * FROM test_type_record_as('tuple', 'three', 3, false);
SELECT * FROM test_type_record_as('tuple', null, null, true);
SELECT * FROM test_type_record_as('list', null, null, false);
SELECT * FROM test_type_record_as('list', 'one', null, false);
SELECT * FROM test_type_record_as('list', null, 2, false);
SELECT * FROM test_type_record_as('list', 'three', 3, false);
SELECT * FROM test_type_record_as('list', null, null, true);
SELECT * FROM test_type_record_as('obj', null, null, false);
SELECT * FROM test_type_record_as('obj', 'one', null, false);
SELECT * FROM test_type_record_as('obj', null, 2, false);
SELECT * FROM test_type_record_as('obj', 'three', 3, false);
SELECT * FROM test_type_record_as('obj', null, null, true);