Enable pl/python to return records based on multiple OUT params
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
Attachments:
plpython.recout.difftext/x-patch; charset=UTF-8; name=plpython.recout.diffDownload
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 */
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
Attachments:
plpython.multiout.difftext/x-patch; charset=utf-8; name=plpython.multiout.diffDownload
? 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);
+
Hannu Krosing <hannu@2ndQuadrant.com> writes:
On Sat, 2008-11-01 at 06:13 +0200, Hannu Krosing wrote:
This version is quite rough, though passes tests here.
I will clean it up more during commitfest.
probably still more things to do
The status of this patch isn't clear --- are you still working on it?
There certainly appear to be a lot of debug leftovers that need to
be removed, error messages to clean up, etc.
regards, tom lane
On Mon, 2008-11-03 at 19:07 -0500, Tom Lane wrote:
Hannu Krosing <hannu@2ndQuadrant.com> writes:
On Sat, 2008-11-01 at 06:13 +0200, Hannu Krosing wrote:
This version is quite rough, though passes tests here.
I will clean it up more during commitfest.
probably still more things to do
The status of this patch isn't clear --- are you still working on it?
There certainly appear to be a lot of debug leftovers that need to
be removed, error messages to clean up, etc.
It passes all existing regression tests and works fine for "correct"
use, but even the code currently in CVS crashes the backend for this
py=# create or replace function add_any(in i1 anyelement, in i2
anyelement, out t text) language plpythonu as $$
return i1 + i2
$$;
CREATE FUNCTION
py=# select * from add_any(1,2);
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!>
Though it is a somewhat separate problem from current patch I'd like to
do something about it before having it all committed, as the fix must
touch the very same places than this patch.
I think it takes two-tree days to figure out proper way to fix it.
I'd like it to just accept ANY* and do the right thing but I may end up
just rejecting ANY* on both IN and OUT args.
------------------------------------------
Hannu Krosing http://www.2ndQuadrant.com
PostgreSQL Scalability and Availability
Services, Consulting and Training
Hannu Krosing <hannu@2ndQuadrant.com> writes:
... even the code currently in CVS crashes the backend for this
py=# create or replace function add_any(in i1 anyelement, in i2
anyelement, out t text) language plpythonu as $$
return i1 + i2
$$;
CREATE FUNCTION
py=# select * from add_any(1,2);
server closed the connection unexpectedly
Well, that's just a stupid uninitialized-variable bug :-(
regards, tom lane
On Tue, 2008-11-04 at 09:57 -0500, Tom Lane wrote:
Hannu Krosing <hannu@2ndQuadrant.com> writes:
... even the code currently in CVS crashes the backend for this
py=# create or replace function add_any(in i1 anyelement, in i2
anyelement, out t text) language plpythonu as $$
return i1 + i2
$$;
CREATE FUNCTION
py=# select * from add_any(1,2);
server closed the connection unexpectedlyWell, that's just a stupid uninitialized-variable bug :-(
there are probably more complex ones, if a ANYELEMENT taking function is
used more than one time, with different types of args
regards, tom lane
--
------------------------------------------
Hannu Krosing http://www.2ndQuadrant.com
PostgreSQL Scalability and Availability
Services, Consulting and Training
On Sat, Nov 1, 2008 at 7:52 AM, Hannu Krosing <hannu@2ndquadrant.com> wrote:
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 recordOverrides previous patch.
Fixed some bugs, added regression tests.
Hi Hannu:
I was wondering if it would be possible to get plpython to convert IN
parameters of type ARRAY to Python lists? I see some example functions
here [1], but it would be nice if it was done automatically.
David
1. http://archives.postgresql.org/pgsql-general/2007-01/msg01417.php
On Tue, 2008-11-04 at 14:05 -0500, David Blewett wrote:
On Sat, Nov 1, 2008 at 7:52 AM, Hannu Krosing <hannu@2ndquadrant.com> wrote:
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 recordOverrides previous patch.
Fixed some bugs, added regression tests.
Hi Hannu:
I was wondering if it would be possible to get plpython to convert IN
parameters of type ARRAY to Python lists? I see some example functions
here [1], but it would be nice if it was done automatically.
This is one thing I definitely will do, praobably right after getting
ANY* to work, maybe even before, if getting ANY* to work requires too
many changes.
One open question is how to translate arrays with non-default subscript
values
Quote: "Subscripted assignment allows creation of arrays that do not use
one-based subscripts. For example one might assign to myarray[-2:7] to
create an array with subscript values running from -2 to 7."
Should I just shift it to standard python tuple, or would it be better
to return it as a dictionary with keys from -2 to 7
sample:
hannu=# create table ta(ia int[]);
CREATE TABLE
hannu=# insert into ta values('{27000,27000}');
INSERT 0 1
hannu=# update ta set ia[-2:1] = '{-2,-1,0,1}';
UPDATE 1
hannu=# select * from ta;
ia
--------------------------
[-2:2]={-2,-1,0,1,27000}
(1 row)
and if I do return a dictionary , the after this
hannu=# update ta set ia[7:7] = '{7}';
UPDATE 1
hannu=# select * from ta;
ia
------------------------------------------------
[-2:7]={-2,-1,0,1,27000,NULL,NULL,NULL,NULL,7}
(1 row)
should the returned python dict have keys 3-6 with None, or could they
just be omitted ?
Actually I have quite long todo list (attached) of what I'd like to do
with pl/python, but I'm not sure how much will be accepted in 8.4 under
current commitfest/feature freeze scheme.
Anyway, I should put that up on wiki for comments.
David
1. http://archives.postgresql.org/pgsql-general/2007-01/msg01417.php
--
------------------------------------------
Hannu Krosing http://www.2ndQuadrant.com
PostgreSQL Scalability and Availability
Services, Consulting and Training
Attachments:
On Tue, Nov 4, 2008 at 4:17 PM, Hannu Krosing <hannu@2ndquadrant.com> wrote:
One open question is how to translate arrays with non-default subscript
valuesQuote: "Subscripted assignment allows creation of arrays that do not use
one-based subscripts. For example one might assign to myarray[-2:7] to
create an array with subscript values running from -2 to 7."Should I just shift it to standard python tuple, or would it be better
to return it as a dictionary with keys from -2 to 7
I think changing the base type is bound to cause issues. For example,
suppose someone expects to be able to simply iterate over the array.
If they're assuming it's a list, they will expect the values to be
returned. If it's a dictionary, the keys will be. If you're going to
do that, you'd need to do a custom dict class that iterated over the
values I think.
David Blewett
Though it is a somewhat separate problem from current patch I'd like to
do something about it before having it all committed, as the fix must
touch the very same places than this patch.I think it takes two-tree days to figure out proper way to fix it.
I'd like it to just accept ANY* and do the right thing but I may end up
just rejecting ANY* on both IN and OUT args.
The text above makes it sound like you're still working on this, but
that was more than three weeks ago. What is the status of this now?
...Robert
On Wed, 2008-11-26 at 23:17 -0500, Robert Haas wrote:
Though it is a somewhat separate problem from current patch I'd like to
do something about it before having it all committed, as the fix must
touch the very same places than this patch.I think it takes two-tree days to figure out proper way to fix it.
I'd like it to just accept ANY* and do the right thing but I may end up
just rejecting ANY* on both IN and OUT args.The text above makes it sound like you're still working on this, but
that was more than three weeks ago. What is the status of this now?
It is ready to be applied without support for ANY*, just with the patch
for uninitialized pointer vector (uses pymalloc0 instead of pymalloc )
tom mentioned.
I am working on getting any supported as well though I was tied up with
other things wor last 1.5 weeks
--
------------------------------------------
Hannu Krosing http://www.2ndQuadrant.com
PostgreSQL Scalability and Availability
Services, Consulting and Training
Hannu Krosing <hannu@2ndQuadrant.com> writes:
On Mon, 2008-11-03 at 19:07 -0500, Tom Lane wrote:
The status of this patch isn't clear --- are you still working on it?
There certainly appear to be a lot of debug leftovers that need to
be removed, error messages to clean up, etc.
It passes all existing regression tests and works fine for "correct"
use,
I'm returning this patch for rework. You still have done nothing about
the above complaints (useless noise added to elog messages, elog(NOTICE)
debug messages that should have been removed, etc). I started to clean
this up myself but got too annoyed when I found that the patch had
removed security-critical checks that reject pseudotype result types.
I have other things to do than clean up WIP patches.
regards, tom lane