On Sat, 2008-11-01 at 06:13 +0200, Hannu Krosing wrote:
> attached is a patch which enables plpython to recognize function with
> multiple OUT params as returning a record

Overrides previous patch.

Fixed some bugs, added regression tests.

> This version is quite rough, though passes tests here.
> 
> I will clean it up more during commitfest.

probably still more things to do

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

Reply via email to