Enable pl/python to return records based on multiple OUT params

Started by Hannu Krosingabout 17 years ago12 messages
#1Hannu Krosing
hannu@2ndQuadrant.com
1 attachment(s)

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 */
#2Hannu Krosing
hannu@2ndQuadrant.com
In reply to: Hannu Krosing (#1)
1 attachment(s)
Re: Enable pl/python to return records based on multiple OUT params

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);
+ 
#3Tom Lane
tgl@sss.pgh.pa.us
In reply to: Hannu Krosing (#2)
Re: Enable pl/python to return records based on multiple OUT params

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

#4Hannu Krosing
hannu@2ndQuadrant.com
In reply to: Tom Lane (#3)
Re: Enable pl/python to return records based on multiple OUT params

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

#5Tom Lane
tgl@sss.pgh.pa.us
In reply to: Hannu Krosing (#4)
Re: Enable pl/python to return records based on multiple OUT params

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

#6Hannu Krosing
hannu@2ndQuadrant.com
In reply to: Tom Lane (#5)
Re: Enable pl/python to return records based on multiple OUT params

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 unexpectedly

Well, 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

#7David Blewett
david@dawninglight.net
In reply to: Hannu Krosing (#2)
Re: Enable pl/python to return records based on multiple OUT params

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 record

Overrides 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

#8Hannu Krosing
hannu@2ndQuadrant.com
In reply to: David Blewett (#7)
1 attachment(s)
Re: ARRAY vars (was Enable pl/python to return records based on multiple OUT params)

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 record

Overrides 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:

TODO.hannutext/plain; charset=utf-8; name=TODO.hannuDownload
#9David Blewett
david@dawninglight.net
In reply to: Hannu Krosing (#8)
Re: ARRAY vars (was Enable pl/python to return records based on multiple OUT params)

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
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

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

#10Robert Haas
robertmhaas@gmail.com
In reply to: Hannu Krosing (#4)
Re: Enable pl/python to return records based on multiple OUT params

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

#11Hannu Krosing
hannu@2ndQuadrant.com
In reply to: Robert Haas (#10)
Re: Enable pl/python to return records based on multiple OUT params

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

#12Tom Lane
tgl@sss.pgh.pa.us
In reply to: Hannu Krosing (#4)
Re: Enable pl/python to return records based on multiple OUT params

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