summaryrefslogtreecommitdiff
path: root/src/pl/plpython/plpython.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pl/plpython/plpython.c')
-rw-r--r--src/pl/plpython/plpython.c425
1 files changed, 407 insertions, 18 deletions
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index f45ecd7222..40d9de37ab 100644
--- a/src/pl/plpython/plpython.c
+++ b/src/pl/plpython/plpython.c
@@ -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 */