diff options
Diffstat (limited to 'src/pl/plpython/plpython.c')
-rw-r--r-- | src/pl/plpython/plpython.c | 425 |
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 */ |