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 ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers