attached is a patch which enables plpython to recognize function with
multiple OUT params as returning a record
py=# create or replace function addsub( in i1 int, in i2 int,
py=# out o1 int, out o2 int) as
py=# $$
py$# return i1 + i2, i1-i2
py$# $$ language plpythonu;
CREATE FUNCTION
py=#
py=# select * from addsub(1,2);
o1 | o2
----+----
3 | -1
(1 row)
This version is quite rough, though passes tests here.
I will clean it up more during commitfest.
--
------------------------------------------
Hannu Krosing http://www.2ndQuadrant.com
PostgreSQL Scalability and Availability
Services, Consulting and Training
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 04:06:02 -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;
} 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 *);
***************
*** 262,268 ****
static PyObject *PLyString_FromString(const char *);
static HeapTuple PLyMapping_ToTuple(PLyTypeInfo *, PyObject *);
! static HeapTuple PLySequence_ToTuple(PLyTypeInfo *, PyObject *);
static HeapTuple PLyObject_ToTuple(PLyTypeInfo *, PyObject *);
/*
--- 264,270 ----
static PyObject *PLyString_FromString(const char *);
static HeapTuple PLyMapping_ToTuple(PLyTypeInfo *, PyObject *);
! static HeapTuple PLySequence_ToTuple(AttInMetadata *, PyObject *);
static HeapTuple PLyObject_ToTuple(PLyTypeInfo *, PyObject *);
/*
***************
*** 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,910 ****
{
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);
--- 904,914 ----
{
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->result, plrv);
***************
*** 1091,1096 ****
--- 1095,1102 ----
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))
{
--- 1136,1142 ----
}
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;
--- 1161,1169 ----
}
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();
{
--- 1201,1217 ----
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 ****
--- 1221,1267 ----
*/
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);
--- 1269,1275 ----
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 */
--- 1353,1359 ----
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)
--- 1534,1551 ----
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]),
--- 1578,1584 ----
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 ****
--- 1606,1612 ----
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);
--- 1621,1627 ----
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 ****
--- 1659,1676 ----
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)
{
***************
*** 1841,1852 ****
static HeapTuple
! PLySequence_ToTuple(PLyTypeInfo * info, PyObject * sequence)
{
! TupleDesc desc;
HeapTuple tuple;
! Datum *values;
! char *nulls;
volatile int i;
Assert(PySequence_Check(sequence));
--- 1918,1929 ----
static HeapTuple
! PLySequence_ToTuple(AttInMetadata *att_info_metadata, PyObject * sequence)
{
! // TupleDesc desc;
HeapTuple tuple;
! char **values;
! // char *nulls;
volatile int i;
Assert(PySequence_Check(sequence));
***************
*** 1856,1922 ****
* 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;
}
--- 1933,1973 ----
* can ignore exceeding items or assume missing ones as null but to avoid
* plpython developer's errors we are strict here
*/
! // desc = att_info_metadata->dupdesc->nattr;
!
! if (PySequence_Length(sequence) != att_info_metadata->tupdesc->natts)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("returned sequence's length must be same as tuple's length")));
! // Assert(info->is_rowtype == 1);
/* Build tuple */
! values = (char**) palloc(sizeof(char*) * att_info_metadata->tupdesc->natts);
! for (i = 0; i < att_info_metadata->tupdesc->natts; ++i)
{
! PyObject *volatile value;
PG_TRY();
{
value = PySequence_GetItem(sequence, i);
Assert(value);
if (value == Py_None)
! values[i] = NULL;
! else {
! values[i] = PyString_AsString(PyObject_Str(value));
}
}
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 */
--- 2451,2457 ----
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 */
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers