1) named parameters additionally to args[] 2) return composite-types from plpython as dictionary 3) return result-set from plpython as list, iterator or generator
Test script attached (patch-test.sql) but not integrated to plpython test-suite.
-- Sven Suursoho
patch-test.sql
Description: Binary data
functions.sql
Description: Binary data
*** postgresql-8.0.7-orig/src/pl/plpython/plpython.c Tue Jan 17 19:33:37 2006 --- postgresql-8.0.7/src/pl/plpython/plpython.c Sat Apr 15 16:16:44 2006 *************** *** 47,52 **** --- 47,53 ---- #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" *************** *** 99,104 **** --- 100,106 ---- { PLyObToDatum *atts; int natts; + Oid typoid; /* Tuple type oid */ } PLyObToTuple; typedef union PLyTypeOutput *************** *** 119,124 **** --- 121,127 ---- /* * is_rowtype can be: -1 not known yet (initial state) 0 scalar * datatype 1 rowtype 2 rowtype, but I/O functions not set up yet + * 3 procedure return composite type I/O is set up */ } PLyTypeInfo; *************** *** 134,139 **** --- 137,147 ---- 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. */ + int setof_count; /* numbef of items to return in result set */ + int setof_current; /* current item in result set */ + char **argnames; /* Argument names */ PLyTypeInfo args[FUNC_MAX_ARGS]; int nargs; PyObject *code; /* compiled procedure code */ *************** *** 216,221 **** --- 224,230 ---- 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 *, *************** *** 251,256 **** --- 260,266 ---- static PyObject *PLyInt_FromString(const char *); static PyObject *PLyLong_FromString(const char *); static PyObject *PLyString_FromString(const char *); + static HeapTuple PLyDict_ToTuple(PLyTypeInfo *, PyObject *); /* global data *************** *** 748,758 **** PG_TRY(); { ! plargs = PLy_function_build_args(fcinfo, proc); ! plrv = PLy_procedure_call(proc, "args", plargs); ! ! Assert(plrv != NULL); ! Assert(!PLy_error_in_progress); /* * Disconnect from SPI manager and then create the return values --- 758,774 ---- PG_TRY(); { ! if (!proc->is_setof || proc->setof_count == -1) ! { ! /* python function not called yet, do it */ ! plargs = PLy_function_build_args(fcinfo, proc); ! plrv = PLy_procedure_call(proc, "args", plargs); ! if (!proc->is_setof) ! /* SETOF function parameters are deleted when called 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 *************** *** 763,768 **** --- 779,854 ---- if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); + if (proc->is_setof) + { + bool is_done = false; + ReturnSetInfo *rsi = (ReturnSetInfo *)fcinfo->resultinfo; + + if (proc->setof_current == -1) + { + /* 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; + + /* fetch information about returned object */ + proc->setof = plrv; + plrv = NULL; + if (PyList_Check(proc->setof)) + /* SETOF as list */ + proc->setof_count = PyList_GET_SIZE(proc->setof); + else if (PyIter_Check(proc->setof)) + /* SETOF as iterator, unknown number of items */ + proc->setof_current = proc->setof_count = 0; + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("SETOF must be returned as list or iterator"))); + } + } + + Assert(proc->setof != NULL); + + /* Fetch next of SETOF */ + if (PyList_Check(proc->setof)) + { + is_done = ++proc->setof_current == proc->setof_count; + if (!is_done) + plrv = PyList_GET_ITEM(proc->setof, proc->setof_current); + } + else if (PyIter_Check(proc->setof)) + { + plrv = PyIter_Next(proc->setof); + is_done = plrv == NULL; + } + + if (!is_done) + { + rsi->isDone = ExprMultipleResult; + } + else + { + rsi->isDone = ExprEndResult; + proc->setof_count = proc->setof_current = -1; + Py_DECREF(proc->setof); + proc->setof = NULL; + + Py_XDECREF(plargs); + Py_XDECREF(plrv); + Py_XDECREF(plrv_so); + + PLy_function_delete_args(proc); + fcinfo->isnull = true; + return (Datum)NULL; + } + } + /* * convert the python PyObject to a postgresql Datum */ *************** *** 771,776 **** --- 857,882 ---- fcinfo->isnull = true; rv = (Datum) NULL; } + else if (proc->result.is_rowtype >= 2) + { + HeapTuple tuple; + + /* returning composite type */ + if (!PyDict_Check(plrv)) + elog(ERROR, "tuple must be returned as dictionary"); + + tuple = PLyDict_ToTuple(&proc->result, plrv); + if (tuple != NULL) + { + fcinfo->isnull = false; + rv = HeapTupleGetDatum(tuple); + } + else + { + fcinfo->isnull = true; + rv = (Datum) NULL; + } + } else { fcinfo->isnull = false; *************** *** 904,909 **** --- 1010,1016 ---- * FIXME -- error check this */ PyList_SetItem(args, i, arg); + PyDict_SetItemString(proc->globals, proc->argnames[i], arg); arg = NULL; } } *************** *** 920,925 **** --- 1027,1042 ---- } + static void + PLy_function_delete_args(PLyProcedure *proc) + { + int i; + + for (i = 0; i < proc->nargs; i++) + PyDict_DelItemString(proc->globals, proc->argnames[i]); + } + + /* * PLyProcedure functions */ *************** *** 990,995 **** --- 1107,1115 ---- bool isnull; int i, rv; + Datum argnames; + Datum *elems; + int nelems; procStruct = (Form_pg_proc) GETSTRUCT(procTup); *************** *** 1023,1028 **** --- 1143,1152 ---- proc->nargs = 0; proc->code = proc->statics = NULL; proc->globals = proc->me = NULL; + proc->is_setof = procStruct->proretset; + proc->setof = NULL; + proc->setof_count = proc->setof_current = -1; + proc->argnames = NULL; PG_TRY(); { *************** *** 1058,1066 **** } if (rvTypeStruct->typtype == 'c') ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("plpython functions cannot return tuples yet"))); else PLy_output_datum_func(&proc->result, rvTypeTup); --- 1182,1192 ---- } if (rvTypeStruct->typtype == 'c') ! { ! /* Tuple: set up later, during first call to PLy_function_handler */ ! proc->result.out.r.typoid = procStruct->prorettype; ! proc->result.is_rowtype = 2; ! } else PLy_output_datum_func(&proc->result, rvTypeTup); *************** *** 1080,1088 **** /* * now get information required for input conversion of the ! * procedures arguments. */ proc->nargs = fcinfo->nargs; for (i = 0; i < fcinfo->nargs; i++) { HeapTuple argTypeTup; --- 1206,1229 ---- /* * now get information required for input conversion of the ! * procedure's arguments. */ proc->nargs = fcinfo->nargs; + proc->argnames = NULL; + if (proc->nargs) + { + argnames = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_proargnames, &isnull); + if (!isnull) + { + deconstruct_array(DatumGetArrayTypeP(argnames), TEXTOID, -1, false, 'i', + &elems, &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; *************** *** 1112,1119 **** * funcs */ ReleaseSysCache(argTypeTup); - } /* * get the text of the function. --- 1253,1269 ---- * funcs */ ReleaseSysCache(argTypeTup); + /* Fetch argument name */ + if (proc->argnames) + { + const char *name; + + name = DatumGetCString(DirectFunctionCall1(textout, elems[i])); + proc->argnames[i] = PLy_malloc(strlen(name) + 1); + strcpy(proc->argnames[i], name); + } + } /* * get the text of the function. *************** *** 1249,1254 **** --- 1399,1405 ---- 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) *************** *** 1256,1261 **** --- 1407,1417 ---- 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 *************** *** 1518,1523 **** --- 1674,1752 ---- return dict; } + + static HeapTuple + PLyDict_ToTuple(PLyTypeInfo *info, PyObject *dict) + { + TupleDesc desc; + HeapTuple tuple; + Datum *values; + char *nulls; + int i; + + desc = CreateTupleDescCopy(lookup_rowtype_tupdesc(info->out.r.typoid, -1)); + + /* Set up tuple type, if neccessary */ + if (info->is_rowtype == 2) + { + PLy_output_tuple_funcs(info, desc); + info->is_rowtype = 3; + } + Assert(info->is_rowtype == 3); + + /* 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 = PyDict_GetItemString(dict, key); + if (value != Py_None && value != NULL) + { + char *valuestr; + + so = PyObject_Str(value); + valuestr = PyString_AsString(so); + values[i] = FunctionCall3(&info->out.r.atts[i].typfunc + , CStringGetDatum(valuestr) + , ObjectIdGetDatum(info->out.r.atts[i].typioparam) + , Int32GetDatum(-1)); + Py_DECREF(so); + value = so = NULL; + nulls[i] = ' '; + } + else + { + value = NULL; + values[i] = (Datum) NULL; + nulls[i] = 'n'; + } + } + PG_CATCH(); + { + Py_XDECREF(value); + Py_XDECREF(so); + PG_RE_THROW(); + } + PG_END_TRY(); + } + + tuple = heap_formtuple(desc, values, nulls); + FreeTupleDesc(desc); + pfree(values); + pfree(nulls); + + return tuple; + } + + /* initialization, some python variables function declared here */ *************** *** 2651,2653 **** --- 2880,2883 ---- { free(ptr); } + /* vim: set noexpandtab nosmarttab shiftwidth=8 cinoptions=l1j1: */
---------------------------(end of broadcast)--------------------------- TIP 9: In versions below 8.0, the planner will ignore your desire to choose an index scan if your joining column's datatypes do not match