On Sat, 2008-11-01 at 06:13 +0200, Hannu Krosing wrote: > attached is a patch which enables plpython to recognize function with > multiple OUT params as returning a record
Overrides previous patch. Fixed some bugs, added regression tests. > This version is quite rough, though passes tests here. > > I will clean it up more during commitfest. probably still more things to do ------------------------------------------ Hannu Krosing http://www.2ndQuadrant.com PostgreSQL Scalability and Availability Services, Consulting and Training
? plpython/.deps ? plpython/gmon.out ? plpython/results Index: plpython/plpython.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/pl/plpython/plpython.c,v retrieving revision 1.114 diff -c -r1.114 plpython.c *** plpython/plpython.c 11 Oct 2008 00:09:33 -0000 1.114 --- plpython/plpython.c 1 Nov 2008 12:48:44 -0000 *************** *** 151,157 **** 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; --- 151,157 ---- PLyTypeInfo result; /* also used to store info for trigger tuple * type */ bool is_setof; /* true, if procedure returns result set */ ! PyObject *setiterator; /* contents of result set. */ char **argnames; /* Argument names */ PLyTypeInfo args[FUNC_MAX_ARGS]; int nargs; *************** *** 160,165 **** --- 160,167 ---- PyObject *globals; /* data saved across calls, global scope */ PyObject *me; /* PyCObject containing pointer to this * PLyProcedure */ + MemoryContext ctx; + AttInMetadata *att_info_metadata; /* for returning composite types */ } PLyProcedure; *************** *** 237,243 **** static PLyProcedure *PLy_procedure_get(FunctionCallInfo fcinfo, Oid tgreloid); ! static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid tgreloid, char *key); static void PLy_procedure_compile(PLyProcedure *, const char *); --- 239,245 ---- static PLyProcedure *PLy_procedure_get(FunctionCallInfo fcinfo, Oid tgreloid); ! static PLyProcedure *PLy_procedure_create(FunctionCallInfo fcinfo,HeapTuple procTup, Oid tgreloid, char *key); static void PLy_procedure_compile(PLyProcedure *, const char *); *************** *** 261,269 **** 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 --- 263,271 ---- static PyObject *PLyLong_FromString(const char *); static PyObject *PLyString_FromString(const char *); ! static HeapTuple PLyMapping_ToTuple(AttInMetadata *, PyObject *); ! static HeapTuple PLySequence_ToTuple(AttInMetadata *, PyObject *); ! static HeapTuple PLyObject_ToTuple(AttInMetadata *, PyObject *); /* * Currently active plpython function *************** *** 783,789 **** PG_TRY(); { ! if (!proc->is_setof || proc->setof == NULL) { /* Simple type returning function or first time for SETOF function */ plargs = PLy_function_build_args(fcinfo, proc); --- 785,791 ---- PG_TRY(); { ! if (!proc->is_setof || proc->setiterator == NULL) { /* Simple type returning function or first time for SETOF function */ plargs = PLy_function_build_args(fcinfo, proc); *************** *** 813,819 **** bool has_error = false; ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; ! if (proc->setof == NULL) { /* first time -- do checks and setup */ if (!rsi || !IsA(rsi, ReturnSetInfo) || --- 815,821 ---- bool has_error = false; ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; ! if (proc->setiterator == NULL) { /* first time -- do checks and setup */ if (!rsi || !IsA(rsi, ReturnSetInfo) || *************** *** 826,844 **** 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 cannot 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 --- 828,846 ---- rsi->returnMode = SFRM_ValuePerCall; /* Make iterator out of returned object */ ! proc->setiterator = PyObject_GetIter(plrv); Py_DECREF(plrv); plrv = NULL; ! if (proc->setiterator == NULL) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("returned object cannot be iterated"), ! errdetail("SETOF must be returned as iterable object"))); } /* Fetch next from iterator */ ! plrv = PyIter_Next(proc->setiterator); if (plrv) rsi->isDone = ExprMultipleResult; else *************** *** 850,857 **** if (rsi->isDone == ExprEndResult) { /* Iterator is exhausted or error happened */ ! Py_DECREF(proc->setof); ! proc->setof = NULL; Py_XDECREF(plargs); Py_XDECREF(plrv); --- 852,859 ---- if (rsi->isDone == ExprEndResult) { /* Iterator is exhausted or error happened */ ! Py_DECREF(proc->setiterator); ! proc->setiterator = NULL; Py_XDECREF(plargs); Py_XDECREF(plrv); *************** *** 902,917 **** { 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; --- 904,919 ---- { HeapTuple tuple = NULL; ! if (PySequence_Check(plrv)) /* composite type as sequence (tuple, list etc) */ ! tuple = PLySequence_ToTuple(proc->att_info_metadata, plrv); else if (PyMapping_Check(plrv)) /* composite type as mapping (currently only dict) */ ! tuple = PLyMapping_ToTuple(proc->att_info_metadata, plrv); else /* returned as smth, must provide method __getattr__(name) */ ! tuple = PLyObject_ToTuple(proc->att_info_metadata, plrv); ! if (tuple != NULL) { fcinfo->isnull = false; *************** *** 1091,1096 **** --- 1093,1100 ---- static PLyProcedure * PLy_procedure_get(FunctionCallInfo fcinfo, Oid tgreloid) { + + Oid fn_oid; HeapTuple procTup; char key[128]; *************** *** 1130,1136 **** } if (proc == NULL) ! proc = PLy_procedure_create(procTup, tgreloid, key); if (OidIsValid(tgreloid)) { --- 1134,1140 ---- } if (proc == NULL) ! proc = PLy_procedure_create(fcinfo, procTup, tgreloid, key); if (OidIsValid(tgreloid)) { *************** *** 1155,1162 **** } static PLyProcedure * ! PLy_procedure_create(HeapTuple procTup, Oid tgreloid, char *key) { char procName[NAMEDATALEN + 256]; Form_pg_proc procStruct; PLyProcedure *volatile proc; --- 1159,1167 ---- } static PLyProcedure * ! PLy_procedure_create(FunctionCallInfo fcinfo, HeapTuple procTup, Oid tgreloid, char *key) { + char procName[NAMEDATALEN + 256]; Form_pg_proc procStruct; PLyProcedure *volatile proc; *************** *** 1194,1204 **** for (i = 0; i < FUNC_MAX_ARGS; i++) PLy_typeinfo_init(&proc->args[i]); 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(); { --- 1199,1215 ---- for (i = 0; i < FUNC_MAX_ARGS; i++) PLy_typeinfo_init(&proc->args[i]); proc->nargs = 0; + proc->att_info_metadata = NULL; proc->code = proc->statics = NULL; proc->globals = proc->me = NULL; proc->is_setof = procStruct->proretset; ! proc->setiterator = NULL; proc->argnames = NULL; + proc->ctx = AllocSetContextCreate(TopMemoryContext, + "PL/Python function context", + ALLOCSET_SMALL_MINSIZE, + ALLOCSET_SMALL_INITSIZE, + ALLOCSET_SMALL_MAXSIZE); PG_TRY(); { *************** *** 1208,1213 **** --- 1219,1265 ---- */ if (!OidIsValid(tgreloid)) { + Oid resultTypeId; + TupleDesc resultTupleDesc; + TypeFuncClass resultType; + + + MemoryContext old_ctx; + HeapTuple rvTypeTup; + + old_ctx = MemoryContextSwitchTo(proc->ctx); + + resultType = get_call_result_type(fcinfo,&resultTypeId,&resultTupleDesc); + + switch(resultType) { + case TYPEFUNC_SCALAR: + rvTypeTup = SearchSysCache(TYPEOID, resultTypeId, 0, 0, 0); + if (!HeapTupleIsValid(rvTypeTup)) + elog(ERROR, "1231: cache lookup failed for type %u", resultTypeId); + PLy_output_datum_func(&proc->result, rvTypeTup); + ReleaseSysCache(rvTypeTup); + break; + + case TYPEFUNC_COMPOSITE: + proc->att_info_metadata = TupleDescGetAttInMetadata(CreateTupleDescCopy(resultTupleDesc)); + proc->result.is_rowtype = 1; + break; + + case TYPEFUNC_RECORD: + elog(NOTICE, "RECORD return type"); + break; + + case TYPEFUNC_OTHER: + elog(NOTICE, "OTHER return type"); + break; + + default: + elog(NOTICE, "unknown return type"); + } + + MemoryContextSwitchTo(old_ctx); + } + if ( 0 ){ HeapTuple rvTypeTup; Form_pg_type rvTypeStruct; *************** *** 1215,1221 **** ObjectIdGetDatum(procStruct->prorettype), 0, 0, 0); if (!HeapTupleIsValid(rvTypeTup)) ! elog(ERROR, "cache lookup failed for type %u", procStruct->prorettype); rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup); --- 1267,1273 ---- ObjectIdGetDatum(procStruct->prorettype), 0, 0, 0); if (!HeapTupleIsValid(rvTypeTup)) ! elog(ERROR, "1262: cache lookup failed for type %u", procStruct->prorettype); rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup); *************** *** 1299,1305 **** ObjectIdGetDatum(types[i]), 0, 0, 0); if (!HeapTupleIsValid(argTypeTup)) ! elog(ERROR, "cache lookup failed for type %u", types[i]); argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup); /* check argument type is OK, set up I/O function info */ --- 1351,1357 ---- ObjectIdGetDatum(types[i]), 0, 0, 0); if (!HeapTupleIsValid(argTypeTup)) ! elog(ERROR, "1346: cache lookup failed for type %u", types[i]); argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup); /* check argument type is OK, set up I/O function info */ *************** *** 1480,1487 **** PLy_free(proc->argnames); } ! /* conversion functions. remember output from python is ! * input to postgresql, and vis versa. */ static void PLy_input_tuple_funcs(PLyTypeInfo * arg, TupleDesc desc) --- 1532,1549 ---- PLy_free(proc->argnames); } ! /* ------------------- ! * conversion functions. ! * ! * remember output from python is input to postgresql, and vis versa. ! * ! * PLy_input_tuple_funcs - sets up arg for receving data from pg tuple ! * ! * PLy_output_tuple_funcs - sets up arg for sending data to pg tuple ! * ! * PLy_output_datum_func - sets up arg for sending data to pg scalar value ! * ! * PLy_output_datum_func2 - does the actual setup for PLy_output_datum_func */ static void PLy_input_tuple_funcs(PLyTypeInfo * arg, TupleDesc desc) *************** *** 1514,1520 **** ObjectIdGetDatum(desc->attrs[i]->atttypid), 0, 0, 0); if (!HeapTupleIsValid(typeTup)) ! elog(ERROR, "cache lookup failed for type %u", desc->attrs[i]->atttypid); PLy_input_datum_func2(&(arg->in.r.atts[i]), --- 1576,1582 ---- ObjectIdGetDatum(desc->attrs[i]->atttypid), 0, 0, 0); if (!HeapTupleIsValid(typeTup)) ! elog(ERROR, "1571: cache lookup failed for type %u", desc->attrs[i]->atttypid); PLy_input_datum_func2(&(arg->in.r.atts[i]), *************** *** 1542,1547 **** --- 1604,1610 ---- arg->out.r.atts = PLy_malloc0(desc->natts * sizeof(PLyDatumToOb)); } + for (i = 0; i < desc->natts; i++) { HeapTuple typeTup; *************** *** 1556,1562 **** ObjectIdGetDatum(desc->attrs[i]->atttypid), 0, 0, 0); if (!HeapTupleIsValid(typeTup)) ! elog(ERROR, "cache lookup failed for type %u", desc->attrs[i]->atttypid); PLy_output_datum_func2(&(arg->out.r.atts[i]), typeTup); --- 1619,1625 ---- ObjectIdGetDatum(desc->attrs[i]->atttypid), 0, 0, 0); if (!HeapTupleIsValid(typeTup)) ! elog(ERROR, "1619: cache lookup failed for type %u", desc->attrs[i]->atttypid); PLy_output_datum_func2(&(arg->out.r.atts[i]), typeTup); *************** *** 1594,1599 **** --- 1657,1674 ---- PLy_input_datum_func2(&(arg->in.d), typeOid, typeTup); } + + /*------------- + * TODO: add support for + * DATE, TIME, DATETIME + * BYTEA + * DECIMAL + * + * ARRAYs of any type + * + * anonymous RECORDS ? + * + */ static void PLy_input_datum_func2(PLyDatumToOb * arg, Oid typeOid, HeapTuple typeTup) { *************** *** 1761,1853 **** static HeapTuple ! PLyMapping_ToTuple(PLyTypeInfo * info, PyObject * mapping) { - TupleDesc desc; HeapTuple tuple; ! Datum *values; ! char *nulls; ! volatile 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 *volatile value, ! *volatile 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, "cannot 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; ! volatile int i; Assert(PySequence_Check(sequence)); --- 1836,1898 ---- static HeapTuple ! PLyMapping_ToTuple(AttInMetadata *att_info_metadata, PyObject * mapping) { HeapTuple tuple; ! char **values; ! volatile int i, tup_natts; Assert(PyMapping_Check(mapping)); ! tup_natts = att_info_metadata->tupdesc->natts; ! /* Build cstrings for tuple field values */ ! values = (char**) palloc(sizeof(char*) * tup_natts); ! for (i = 0; i < tup_natts; ++i) ! { ! char *key; ! PyObject *volatile value; ! ! key = NameStr(att_info_metadata->tupdesc->attrs[i]->attname); ! // elog(NOTICE, "Mapping_ToTuple mapping %d, key %s", mapping, key); ! PG_TRY(); { value = PyMapping_GetItemString(mapping, key); if (value == Py_None) ! values[i] = NULL; else if (value) ! values[i] = PyString_AsString(PyObject_Str(value)); ! else ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), ! errmsg("no key named \"%s\"", key), ! errhint("to return null in specific column, " ! "let returned dict to have key named " ! "after column with value None"))); } PG_CATCH(); { Py_XDECREF(value); PG_RE_THROW(); } PG_END_TRY(); } ! tuple = BuildTupleFromCStrings(att_info_metadata, values); pfree(values); return tuple; } static HeapTuple ! PLySequence_ToTuple(AttInMetadata *att_info_metadata, PyObject * sequence) { HeapTuple tuple; ! char **values; ! volatile int i, tup_natts; Assert(PySequence_Check(sequence)); *************** *** 1856,2000 **** * 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 *volatile value, ! *volatile 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, "cannot 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; ! volatile 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 *volatile value, ! *volatile 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, "cannot 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; } --- 1901,1989 ---- * can ignore exceeding items or assume missing ones as null but to avoid * plpython developer's errors we are strict here */ ! ! tup_natts = att_info_metadata->tupdesc->natts; ! ! if (PySequence_Length(sequence) != tup_natts) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("returned sequence's length must be same as tuple's length"))); ! /* Build cstrings for tuple field values */ ! values = (char**) palloc(sizeof(char*) * tup_natts); ! for (i = 0; i < tup_natts; ++i) { ! PyObject *volatile value; PG_TRY(); { value = PySequence_GetItem(sequence, i); Assert(value); if (value == Py_None) ! values[i] = NULL; else if (value) ! values[i] = PyString_AsString(PyObject_Str(value)); ! else ! ereport(ERROR, ! (errcode(ERRCODE_UNDEFINED_COLUMN), ! errmsg("could not convert column \"%d\"", i))); } PG_CATCH(); { Py_XDECREF(value); PG_RE_THROW(); } PG_END_TRY(); } ! tuple = BuildTupleFromCStrings(att_info_metadata, values); pfree(values); return tuple; } static HeapTuple ! PLyObject_ToTuple(AttInMetadata *att_info_metadata, PyObject * object) { HeapTuple tuple; ! char **values; ! volatile int i, tup_natts; ! ! tup_natts = att_info_metadata->tupdesc->natts; ! /* Build cstrings for tuple field values */ ! values = (char**) palloc(sizeof(char*) * tup_natts); ! for (i = 0; i < tup_natts; ++i) ! { ! char *key; ! PyObject *volatile value; ! key = NameStr(att_info_metadata->tupdesc->attrs[i]->attname); ! PG_TRY(); { value = PyObject_GetAttrString(object, key); if (value == Py_None) ! values[i] = NULL; ! else if(value) ! values[i] = PyString_AsString(PyObject_Str(value)); 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"))); } PG_CATCH(); { Py_XDECREF(value); PG_RE_THROW(); } PG_END_TRY(); } ! tuple = BuildTupleFromCStrings(att_info_metadata, values); pfree(values); return tuple; } *************** *** 2400,2406 **** ObjectIdGetDatum(typeId), 0, 0, 0); if (!HeapTupleIsValid(typeTup)) ! elog(ERROR, "cache lookup failed for type %u", typeId); Py_DECREF(optr); optr = NULL; /* this is important */ --- 2389,2395 ---- ObjectIdGetDatum(typeId), 0, 0, 0); if (!HeapTupleIsValid(typeTup)) ! elog(ERROR, "2475: cache lookup failed for type %u", typeId); Py_DECREF(optr); optr = NULL; /* this is important */ Index: plpython/expected/plpython_function.out =================================================================== RCS file: /projects/cvsroot/pgsql/src/pl/plpython/expected/plpython_function.out,v retrieving revision 1.11 diff -c -r1.11 plpython_function.out *** plpython/expected/plpython_function.out 3 May 2008 02:47:47 -0000 1.11 --- plpython/expected/plpython_function.out 1 Nov 2008 12:48:44 -0000 *************** *** 442,449 **** -- this doesn't work yet :-( CREATE FUNCTION test_in_out_params_multi(first in text, second out text, third out text) AS $$ ! return first + '_record_in_to_out'; $$ LANGUAGE plpythonu; CREATE FUNCTION test_inout_params(first inout text) AS $$ return first + '_inout'; $$ LANGUAGE plpythonu; --- 442,481 ---- -- this doesn't work yet :-( CREATE FUNCTION test_in_out_params_multi(first in text, second out text, third out text) AS $$ ! return first + '_record_in_to_out', 'third'; $$ LANGUAGE plpythonu; CREATE FUNCTION test_inout_params(first inout text) AS $$ return first + '_inout'; $$ LANGUAGE plpythonu; + CREATE OR REPLACE FUNCTION addsub( in i1 int, in i2 int, out o1 int, out o2 int) as $$ + return i1 + i2, i1-i2 + $$ LANGUAGE plpythonu; + CREATE OR REPLACE FUNCTION addsub_set( in i1 int, in i2 int, out o1 int, out o2 int) + RETURNS SETOF RECORD AS $$ + for i in range(1,5): + yield i1 + i2, i1-i2 + i + $$ LANGUAGE plpythonu; + CREATE OR REPLACE FUNCTION addsub_dict( in i1 int, in i2 int, out o1 int, out o2 int) as $$ + return {'o2': i1 + i2, 'o1': i1-i2 } + $$ LANGUAGE plpythonu; + CREATE OR REPLACE FUNCTION addsub_dictset( in i1 int, in i2 int, out o1 int, out o2 int) + RETURNS SETOF RECORD AS $$ + for i in range(1,5): + yield {'o2': i1 + i2, 'o1': i1-i2 } + $$ LANGUAGE plpythonu; + CREATE OR REPLACE FUNCTION addsub_obj( in i1 int, in i2 int, out o1 int, out o2 int) as $$ + class ret: + def __init__(self, o1, o2): + self.o1 = o1 + self.o2 = o2 + return ret(i1 + i2, i1-i2 ) + $$ LANGUAGE plpythonu; + CREATE OR REPLACE FUNCTION addsub_objset( in i1 int, in i2 int, out o1 int, out o2 int) + RETURNS SETOF RECORD as $$ + class ret: + def __init__(self, o1, o2): + self.o1 = o1 + self.o2 = o2 + for i in range(1,5): + yield ret(i1 + i2 + i, i1-i2 ) + $$ LANGUAGE plpythonu; Index: plpython/expected/plpython_test.out =================================================================== RCS file: /projects/cvsroot/pgsql/src/pl/plpython/expected/plpython_test.out,v retrieving revision 1.6 diff -c -r1.6 plpython_test.out *** plpython/expected/plpython_test.out 3 May 2008 02:47:47 -0000 1.6 --- plpython/expected/plpython_test.out 1 Nov 2008 12:48:44 -0000 *************** *** 545,556 **** test_in_in_to_out (1 row) - -- this doesn't work yet :-( SELECT * FROM test_in_out_params_multi('test_in'); ! ERROR: plpython functions cannot return type record SELECT * FROM test_inout_params('test_in'); first --------------- test_in_inout (1 row) --- 545,604 ---- test_in_in_to_out (1 row) SELECT * FROM test_in_out_params_multi('test_in'); ! second | third ! --------------------------+------- ! test_in_record_in_to_out | third ! (1 row) ! SELECT * FROM test_inout_params('test_in'); first --------------- test_in_inout (1 row) + SELECT * FROM addsub(1,2); + o1 | o2 + ----+---- + 3 | -1 + (1 row) + + SELECT * FROM addsub_set(1,2); + o1 | o2 + ----+---- + 3 | 0 + 3 | 1 + 3 | 2 + 3 | 3 + (4 rows) + + SELECT * FROM addsub_dict(1,2); + o1 | o2 + ----+---- + -1 | 3 + (1 row) + + SELECT * FROM addsub_dictset(1,2); + o1 | o2 + ----+---- + -1 | 3 + -1 | 3 + -1 | 3 + -1 | 3 + (4 rows) + + SELECT * FROM addsub_obj(1,2); + o1 | o2 + ----+---- + 3 | -1 + (1 row) + + SELECT * FROM addsub_objset(1,2); + o1 | o2 + ----+---- + 4 | -1 + 5 | -1 + 6 | -1 + 7 | -1 + (4 rows) + Index: plpython/sql/plpython_function.sql =================================================================== RCS file: /projects/cvsroot/pgsql/src/pl/plpython/sql/plpython_function.sql,v retrieving revision 1.11 diff -c -r1.11 plpython_function.sql *** plpython/sql/plpython_function.sql 3 May 2008 02:47:48 -0000 1.11 --- plpython/sql/plpython_function.sql 1 Nov 2008 12:48:44 -0000 *************** *** 487,495 **** -- this doesn't work yet :-( CREATE FUNCTION test_in_out_params_multi(first in text, second out text, third out text) AS $$ ! return first + '_record_in_to_out'; $$ LANGUAGE plpythonu; CREATE FUNCTION test_inout_params(first inout text) AS $$ return first + '_inout'; $$ LANGUAGE plpythonu; --- 487,537 ---- -- this doesn't work yet :-( CREATE FUNCTION test_in_out_params_multi(first in text, second out text, third out text) AS $$ ! return first + '_record_in_to_out', 'third'; $$ LANGUAGE plpythonu; CREATE FUNCTION test_inout_params(first inout text) AS $$ return first + '_inout'; $$ LANGUAGE plpythonu; + + CREATE OR REPLACE FUNCTION addsub( in i1 int, in i2 int, out o1 int, out o2 int) as $$ + return i1 + i2, i1-i2 + $$ LANGUAGE plpythonu; + + CREATE OR REPLACE FUNCTION addsub_set( in i1 int, in i2 int, out o1 int, out o2 int) + RETURNS SETOF RECORD AS $$ + for i in range(1,5): + yield i1 + i2, i1-i2 + i + $$ LANGUAGE plpythonu; + + + CREATE OR REPLACE FUNCTION addsub_dict( in i1 int, in i2 int, out o1 int, out o2 int) as $$ + return {'o2': i1 + i2, 'o1': i1-i2 } + $$ LANGUAGE plpythonu; + + CREATE OR REPLACE FUNCTION addsub_dictset( in i1 int, in i2 int, out o1 int, out o2 int) + RETURNS SETOF RECORD AS $$ + for i in range(1,5): + yield {'o2': i1 + i2, 'o1': i1-i2 } + $$ LANGUAGE plpythonu; + + CREATE OR REPLACE FUNCTION addsub_obj( in i1 int, in i2 int, out o1 int, out o2 int) as $$ + class ret: + def __init__(self, o1, o2): + self.o1 = o1 + self.o2 = o2 + return ret(i1 + i2, i1-i2 ) + $$ LANGUAGE plpythonu; + + CREATE OR REPLACE FUNCTION addsub_objset( in i1 int, in i2 int, out o1 int, out o2 int) + RETURNS SETOF RECORD as $$ + class ret: + def __init__(self, o1, o2): + self.o1 = o1 + self.o2 = o2 + for i in range(1,5): + yield ret(i1 + i2 + i, i1-i2 ) + $$ LANGUAGE plpythonu; + + + Index: plpython/sql/plpython_test.sql =================================================================== RCS file: /projects/cvsroot/pgsql/src/pl/plpython/sql/plpython_test.sql,v retrieving revision 1.4 diff -c -r1.4 plpython_test.sql *** plpython/sql/plpython_test.sql 3 May 2008 02:47:48 -0000 1.4 --- plpython/sql/plpython_test.sql 1 Nov 2008 12:48:44 -0000 *************** *** 145,150 **** SELECT * FROM test_type_record_as('obj', null, null, true); SELECT * FROM test_in_out_params('test_in'); - -- this doesn't work yet :-( SELECT * FROM test_in_out_params_multi('test_in'); SELECT * FROM test_inout_params('test_in'); --- 145,157 ---- SELECT * FROM test_type_record_as('obj', null, null, true); SELECT * FROM test_in_out_params('test_in'); SELECT * FROM test_in_out_params_multi('test_in'); SELECT * FROM test_inout_params('test_in'); + + SELECT * FROM addsub(1,2); + SELECT * FROM addsub_set(1,2); + SELECT * FROM addsub_dict(1,2); + SELECT * FROM addsub_dictset(1,2); + SELECT * FROM addsub_obj(1,2); + SELECT * FROM addsub_objset(1,2); +
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers