attached is a patch which enables plpython to recognize function with
multiple OUT params as returning a record


py=# create or replace function addsub( in i1 int, in i2 int, 
py=#   out o1 int, out o2 int) as 
py=# $$
py$# return i1 + i2, i1-i2
py$# $$  language plpythonu;
CREATE FUNCTION
py=# 
py=# select * from addsub(1,2);
 o1 | o2 
----+----
  3 | -1
(1 row)


This version is quite rough, though passes tests here.

I will clean it up more during commitfest.

-- 
------------------------------------------
Hannu Krosing   http://www.2ndQuadrant.com
PostgreSQL Scalability and Availability 
   Services, Consulting and Training
Index: plpython/plpython.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/pl/plpython/plpython.c,v
retrieving revision 1.114
diff -c -r1.114 plpython.c
*** plpython/plpython.c	11 Oct 2008 00:09:33 -0000	1.114
--- plpython/plpython.c	1 Nov 2008 04:06:02 -0000
***************
*** 151,157 ****
  	PLyTypeInfo result;			/* also used to store info for trigger tuple
  								 * type */
  	bool		is_setof;		/* true, if procedure returns result set */
! 	PyObject   *setof;			/* contents of result set. */
  	char	  **argnames;		/* Argument names */
  	PLyTypeInfo args[FUNC_MAX_ARGS];
  	int			nargs;
--- 151,157 ----
  	PLyTypeInfo result;			/* also used to store info for trigger tuple
  								 * type */
  	bool		is_setof;		/* true, if procedure returns result set */
! 	PyObject   *setiterator;			/* contents of result set. */
  	char	  **argnames;		/* Argument names */
  	PLyTypeInfo args[FUNC_MAX_ARGS];
  	int			nargs;
***************
*** 160,165 ****
--- 160,167 ----
  	PyObject   *globals;		/* data saved across calls, global scope */
  	PyObject   *me;				/* PyCObject containing pointer to this
  								 * PLyProcedure */
+ 	MemoryContext  ctx;
+ 	AttInMetadata *att_info_metadata;
  }	PLyProcedure;
  
  
***************
*** 237,243 ****
  static PLyProcedure *PLy_procedure_get(FunctionCallInfo fcinfo,
  				  Oid tgreloid);
  
! static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid tgreloid,
  										  char *key);
  
  static void PLy_procedure_compile(PLyProcedure *, const char *);
--- 239,245 ----
  static PLyProcedure *PLy_procedure_get(FunctionCallInfo fcinfo,
  				  Oid tgreloid);
  
! static PLyProcedure *PLy_procedure_create(FunctionCallInfo fcinfo,HeapTuple procTup, Oid tgreloid,
  										  char *key);
  
  static void PLy_procedure_compile(PLyProcedure *, const char *);
***************
*** 262,268 ****
  static PyObject *PLyString_FromString(const char *);
  
  static HeapTuple PLyMapping_ToTuple(PLyTypeInfo *, PyObject *);
! static HeapTuple PLySequence_ToTuple(PLyTypeInfo *, PyObject *);
  static HeapTuple PLyObject_ToTuple(PLyTypeInfo *, PyObject *);
  
  /*
--- 264,270 ----
  static PyObject *PLyString_FromString(const char *);
  
  static HeapTuple PLyMapping_ToTuple(PLyTypeInfo *, PyObject *);
! static HeapTuple PLySequence_ToTuple(AttInMetadata *, PyObject *);
  static HeapTuple PLyObject_ToTuple(PLyTypeInfo *, PyObject *);
  
  /*
***************
*** 783,789 ****
  
  	PG_TRY();
  	{
! 		if (!proc->is_setof || proc->setof == NULL)
  		{
  			/* Simple type returning function or first time for SETOF function */
  			plargs = PLy_function_build_args(fcinfo, proc);
--- 785,791 ----
  
  	PG_TRY();
  	{
! 		if (!proc->is_setof || proc->setiterator == NULL)
  		{
  			/* Simple type returning function or first time for SETOF function */
  			plargs = PLy_function_build_args(fcinfo, proc);
***************
*** 813,819 ****
  			bool		has_error = false;
  			ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
  
! 			if (proc->setof == NULL)
  			{
  				/* first time -- do checks and setup */
  				if (!rsi || !IsA(rsi, ReturnSetInfo) ||
--- 815,821 ----
  			bool		has_error = false;
  			ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
  
! 			if (proc->setiterator == NULL)
  			{
  				/* first time -- do checks and setup */
  				if (!rsi || !IsA(rsi, ReturnSetInfo) ||
***************
*** 826,844 ****
  				rsi->returnMode = SFRM_ValuePerCall;
  
  				/* Make iterator out of returned object */
! 				proc->setof = PyObject_GetIter(plrv);
  				Py_DECREF(plrv);
  				plrv = NULL;
  
! 				if (proc->setof == NULL)
  					ereport(ERROR,
  							(errcode(ERRCODE_DATATYPE_MISMATCH),
  							 errmsg("returned object cannot be iterated"),
! 					errdetail("SETOF must be returned as iterable object")));
  			}
  
  			/* Fetch next from iterator */
! 			plrv = PyIter_Next(proc->setof);
  			if (plrv)
  				rsi->isDone = ExprMultipleResult;
  			else
--- 828,846 ----
  				rsi->returnMode = SFRM_ValuePerCall;
  
  				/* Make iterator out of returned object */
! 				proc->setiterator = PyObject_GetIter(plrv);
  				Py_DECREF(plrv);
  				plrv = NULL;
  
! 				if (proc->setiterator == NULL)
  					ereport(ERROR,
  							(errcode(ERRCODE_DATATYPE_MISMATCH),
  							 errmsg("returned object cannot be iterated"),
! 							 errdetail("SETOF must be returned as iterable object")));
  			}
  
  			/* Fetch next from iterator */
! 			plrv = PyIter_Next(proc->setiterator);
  			if (plrv)
  				rsi->isDone = ExprMultipleResult;
  			else
***************
*** 850,857 ****
  			if (rsi->isDone == ExprEndResult)
  			{
  				/* Iterator is exhausted or error happened */
! 				Py_DECREF(proc->setof);
! 				proc->setof = NULL;
  
  				Py_XDECREF(plargs);
  				Py_XDECREF(plrv);
--- 852,859 ----
  			if (rsi->isDone == ExprEndResult)
  			{
  				/* Iterator is exhausted or error happened */
! 				Py_DECREF(proc->setiterator);
! 				proc->setiterator = NULL;
  
  				Py_XDECREF(plargs);
  				Py_XDECREF(plrv);
***************
*** 902,910 ****
  		{
  			HeapTuple	tuple = NULL;
  
! 			if (PySequence_Check(plrv))
  				/* composite type as sequence (tuple, list etc) */
! 				tuple = PLySequence_ToTuple(&proc->result, plrv);
  			else if (PyMapping_Check(plrv))
  				/* composite type as mapping (currently only dict) */
  				tuple = PLyMapping_ToTuple(&proc->result, plrv);
--- 904,914 ----
  		{
  			HeapTuple	tuple = NULL;
  
! 			if (PySequence_Check(plrv)) 
! 			{
  				/* composite type as sequence (tuple, list etc) */
! 				tuple = PLySequence_ToTuple(proc->att_info_metadata, plrv);
! 			}
  			else if (PyMapping_Check(plrv))
  				/* composite type as mapping (currently only dict) */
  				tuple = PLyMapping_ToTuple(&proc->result, plrv);
***************
*** 1091,1096 ****
--- 1095,1102 ----
  static PLyProcedure *
  PLy_procedure_get(FunctionCallInfo fcinfo, Oid tgreloid)
  {
+ 
+ 
  	Oid			fn_oid;
  	HeapTuple	procTup;
  	char		key[128];
***************
*** 1130,1136 ****
  	}
  
  	if (proc == NULL)
! 		proc = PLy_procedure_create(procTup, tgreloid, key);
  
  	if (OidIsValid(tgreloid))
  	{
--- 1136,1142 ----
  	}
  
  	if (proc == NULL)
! 		proc = PLy_procedure_create(fcinfo, procTup, tgreloid, key);
  
  	if (OidIsValid(tgreloid))
  	{
***************
*** 1155,1162 ****
  }
  
  static PLyProcedure *
! PLy_procedure_create(HeapTuple procTup, Oid tgreloid, char *key)
  {
  	char		procName[NAMEDATALEN + 256];
  	Form_pg_proc procStruct;
  	PLyProcedure *volatile proc;
--- 1161,1169 ----
  }
  
  static PLyProcedure *
! PLy_procedure_create(FunctionCallInfo fcinfo, HeapTuple procTup, Oid tgreloid, char *key)
  {
+ 
  	char		procName[NAMEDATALEN + 256];
  	Form_pg_proc procStruct;
  	PLyProcedure *volatile proc;
***************
*** 1194,1204 ****
  	for (i = 0; i < FUNC_MAX_ARGS; i++)
  		PLy_typeinfo_init(&proc->args[i]);
  	proc->nargs = 0;
  	proc->code = proc->statics = NULL;
  	proc->globals = proc->me = NULL;
  	proc->is_setof = procStruct->proretset;
! 	proc->setof = NULL;
  	proc->argnames = NULL;
  
  	PG_TRY();
  	{
--- 1201,1217 ----
  	for (i = 0; i < FUNC_MAX_ARGS; i++)
  		PLy_typeinfo_init(&proc->args[i]);
  	proc->nargs = 0;
+ 	proc->att_info_metadata = NULL;
  	proc->code = proc->statics = NULL;
  	proc->globals = proc->me = NULL;
  	proc->is_setof = procStruct->proretset;
! 	proc->setiterator = NULL;
  	proc->argnames = NULL;
+ 	proc->ctx = AllocSetContextCreate(TopMemoryContext,
+ 					              "PL/Python function context",
+ 								  ALLOCSET_SMALL_MINSIZE,
+ 								  ALLOCSET_SMALL_INITSIZE,
+ 								  ALLOCSET_SMALL_MAXSIZE);
  
  	PG_TRY();
  	{
***************
*** 1208,1213 ****
--- 1221,1267 ----
  		 */
  		if (!OidIsValid(tgreloid))
  		{
+ 			Oid				resultTypeId;
+ 			TupleDesc		resultTupleDesc;
+ 			TypeFuncClass	resultType;
+ 
+ 	
+ 			MemoryContext	old_ctx;
+ 			HeapTuple		rvTypeTup;
+ 
+ 			old_ctx = MemoryContextSwitchTo(proc->ctx);
+ 
+ 			resultType = get_call_result_type(fcinfo,&resultTypeId,&resultTupleDesc);
+ 
+ 			switch(resultType) {
+ 				case TYPEFUNC_SCALAR:
+ 					rvTypeTup = SearchSysCache(TYPEOID, resultTypeId,  0, 0, 0);
+ 					if (!HeapTupleIsValid(rvTypeTup))
+ 							elog(ERROR, "1231: cache lookup failed for type %u", resultTypeId);
+ 					PLy_output_datum_func(&proc->result, rvTypeTup);
+ 					ReleaseSysCache(rvTypeTup);
+ 					break;
+ 
+ 				case TYPEFUNC_COMPOSITE:
+ 					proc->att_info_metadata = TupleDescGetAttInMetadata(CreateTupleDescCopy(resultTupleDesc));
+ 					proc->result.is_rowtype = 1;
+ 					break;
+ 
+ 				case TYPEFUNC_RECORD:
+ 					elog(NOTICE, "RECORD return type");
+ 					break;
+ 
+ 				case TYPEFUNC_OTHER:
+ 					elog(NOTICE, "OTHER return type");
+ 					break;
+ 
+ 				default:
+ 					elog(NOTICE, "unknown return type");
+ 			}
+ 
+ 			MemoryContextSwitchTo(old_ctx);
+ 		}
+ 		if ( 0 ){
  			HeapTuple	rvTypeTup;
  			Form_pg_type rvTypeStruct;
  
***************
*** 1215,1221 ****
  									ObjectIdGetDatum(procStruct->prorettype),
  									   0, 0, 0);
  			if (!HeapTupleIsValid(rvTypeTup))
! 				elog(ERROR, "cache lookup failed for type %u",
  					 procStruct->prorettype);
  			rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup);
  
--- 1269,1275 ----
  									ObjectIdGetDatum(procStruct->prorettype),
  									   0, 0, 0);
  			if (!HeapTupleIsValid(rvTypeTup))
! 				elog(ERROR, "1262: cache lookup failed for type %u",
  					 procStruct->prorettype);
  			rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup);
  
***************
*** 1299,1305 ****
  											ObjectIdGetDatum(types[i]),
  											0, 0, 0);
  				if (!HeapTupleIsValid(argTypeTup))
! 					elog(ERROR, "cache lookup failed for type %u", types[i]);
  				argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup);
  
  				/* check argument type is OK, set up I/O function info */
--- 1353,1359 ----
  											ObjectIdGetDatum(types[i]),
  											0, 0, 0);
  				if (!HeapTupleIsValid(argTypeTup))
! 					elog(ERROR, "1346: cache lookup failed for type %u", types[i]);
  				argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup);
  
  				/* check argument type is OK, set up I/O function info */
***************
*** 1480,1487 ****
  		PLy_free(proc->argnames);
  }
  
! /* conversion functions.  remember output from python is
!  * input to postgresql, and vis versa.
   */
  static void
  PLy_input_tuple_funcs(PLyTypeInfo * arg, TupleDesc desc)
--- 1534,1551 ----
  		PLy_free(proc->argnames);
  }
  
! /* -------------------
!  *   conversion functions.  
!  *
!  * remember output from python is input to postgresql, and vis versa.
!  *
!  * PLy_input_tuple_funcs - sets up arg for receving data from pg tuple
!  *
!  * PLy_output_tuple_funcs - sets up arg for sending data to pg tuple 
!  *
!  * PLy_output_datum_func -  sets up arg for sending data to pg scalar value
!  *
!  * PLy_output_datum_func2 - does the actual setup for PLy_output_datum_func
   */
  static void
  PLy_input_tuple_funcs(PLyTypeInfo * arg, TupleDesc desc)
***************
*** 1514,1520 ****
  								 ObjectIdGetDatum(desc->attrs[i]->atttypid),
  								 0, 0, 0);
  		if (!HeapTupleIsValid(typeTup))
! 			elog(ERROR, "cache lookup failed for type %u",
  				 desc->attrs[i]->atttypid);
  
  		PLy_input_datum_func2(&(arg->in.r.atts[i]),
--- 1578,1584 ----
  								 ObjectIdGetDatum(desc->attrs[i]->atttypid),
  								 0, 0, 0);
  		if (!HeapTupleIsValid(typeTup))
! 			elog(ERROR, "1571: cache lookup failed for type %u",
  				 desc->attrs[i]->atttypid);
  
  		PLy_input_datum_func2(&(arg->in.r.atts[i]),
***************
*** 1542,1547 ****
--- 1606,1612 ----
  		arg->out.r.atts = PLy_malloc0(desc->natts * sizeof(PLyDatumToOb));
  	}
  
+ 
  	for (i = 0; i < desc->natts; i++)
  	{
  		HeapTuple	typeTup;
***************
*** 1556,1562 ****
  								 ObjectIdGetDatum(desc->attrs[i]->atttypid),
  								 0, 0, 0);
  		if (!HeapTupleIsValid(typeTup))
! 			elog(ERROR, "cache lookup failed for type %u",
  				 desc->attrs[i]->atttypid);
  
  		PLy_output_datum_func2(&(arg->out.r.atts[i]), typeTup);
--- 1621,1627 ----
  								 ObjectIdGetDatum(desc->attrs[i]->atttypid),
  								 0, 0, 0);
  		if (!HeapTupleIsValid(typeTup))
! 			elog(ERROR, "1619: cache lookup failed for type %u",
  				 desc->attrs[i]->atttypid);
  
  		PLy_output_datum_func2(&(arg->out.r.atts[i]), typeTup);
***************
*** 1594,1599 ****
--- 1659,1676 ----
  	PLy_input_datum_func2(&(arg->in.d), typeOid, typeTup);
  }
  
+ 
+ /*-------------
+  * TODO: add support for
+  * DATE, TIME, DATETIME
+  * BYTEA
+  * DECIMAL
+  *
+  * ARRAYs of any type
+  *
+  * anonymous RECORDS ?
+  *
+  */
  static void
  PLy_input_datum_func2(PLyDatumToOb * arg, Oid typeOid, HeapTuple typeTup)
  {
***************
*** 1841,1852 ****
  
  
  static HeapTuple
! PLySequence_ToTuple(PLyTypeInfo * info, PyObject * sequence)
  {
! 	TupleDesc	desc;
  	HeapTuple	tuple;
! 	Datum	   *values;
! 	char	   *nulls;
  	volatile int i;
  
  	Assert(PySequence_Check(sequence));
--- 1918,1929 ----
  
  
  static HeapTuple
! PLySequence_ToTuple(AttInMetadata *att_info_metadata, PyObject * sequence)
  {
! //	TupleDesc	desc;
  	HeapTuple	tuple;
! 	char	   **values;
! //	char	   *nulls;
  	volatile int i;
  
  	Assert(PySequence_Check(sequence));
***************
*** 1856,1922 ****
  	 * can ignore exceeding items or assume missing ones as null but to avoid
  	 * plpython developer's errors we are strict here
  	 */
! 	desc = lookup_rowtype_tupdesc(info->out.d.typoid, -1);
! 	if (PySequence_Length(sequence) != desc->natts)
  		ereport(ERROR,
  				(errcode(ERRCODE_DATATYPE_MISMATCH),
  		errmsg("returned sequence's length must be same as tuple's length")));
  
! 	if (info->is_rowtype == 2)
! 		PLy_output_tuple_funcs(info, desc);
! 	Assert(info->is_rowtype == 1);
  
  	/* Build tuple */
! 	values = palloc(sizeof(Datum) * desc->natts);
! 	nulls = palloc(sizeof(char) * desc->natts);
! 	for (i = 0; i < desc->natts; ++i)
  	{
! 		PyObject   *volatile value,
! 				   *volatile so;
! 
! 		value = so = NULL;
  		PG_TRY();
  		{
  			value = PySequence_GetItem(sequence, i);
  			Assert(value);
  			if (value == Py_None)
! 			{
! 				values[i] = (Datum) NULL;
! 				nulls[i] = 'n';
! 			}
! 			else if (value)
! 			{
! 				char	   *valuestr;
! 
! 				so = PyObject_Str(value);
! 				if (so == NULL)
! 					PLy_elog(ERROR, "cannot convert sequence type");
! 				valuestr = PyString_AsString(so);
! 				values[i] = InputFunctionCall(&info->out.r.atts[i].typfunc
! 											  ,valuestr
! 											  ,info->out.r.atts[i].typioparam
! 											  ,-1);
! 				Py_DECREF(so);
! 				so = NULL;
! 				nulls[i] = ' ';
  			}
- 
- 			Py_XDECREF(value);
- 			value = NULL;
  		}
  		PG_CATCH();
  		{
- 			Py_XDECREF(so);
  			Py_XDECREF(value);
  			PG_RE_THROW();
  		}
  		PG_END_TRY();
  	}
  
! 	tuple = heap_formtuple(desc, values, nulls);
! 	ReleaseTupleDesc(desc);
  	pfree(values);
- 	pfree(nulls);
  
  	return tuple;
  }
--- 1933,1973 ----
  	 * can ignore exceeding items or assume missing ones as null but to avoid
  	 * plpython developer's errors we are strict here
  	 */
! //	desc = att_info_metadata->dupdesc->nattr;
! 
! 	if (PySequence_Length(sequence) != att_info_metadata->tupdesc->natts)
  		ereport(ERROR,
  				(errcode(ERRCODE_DATATYPE_MISMATCH),
  		errmsg("returned sequence's length must be same as tuple's length")));
  
! //	Assert(info->is_rowtype == 1);
  
  	/* Build tuple */
! 	values = (char**) palloc(sizeof(char*) * att_info_metadata->tupdesc->natts);
! 	for (i = 0; i < att_info_metadata->tupdesc->natts; ++i)
  	{
! 		PyObject   *volatile value;
  		PG_TRY();
  		{
  			value = PySequence_GetItem(sequence, i);
  			Assert(value);
  			if (value == Py_None)
! 				values[i] = NULL;
! 			else {
! 				values[i] = PyString_AsString(PyObject_Str(value));
  			}
  		}
  		PG_CATCH();
  		{
  			Py_XDECREF(value);
  			PG_RE_THROW();
  		}
  		PG_END_TRY();
  	}
  
! 	tuple = BuildTupleFromCStrings(att_info_metadata, values);
! 
  	pfree(values);
  
  	return tuple;
  }
***************
*** 2400,2406 ****
  											 ObjectIdGetDatum(typeId),
  											 0, 0, 0);
  					if (!HeapTupleIsValid(typeTup))
! 						elog(ERROR, "cache lookup failed for type %u", typeId);
  
  					Py_DECREF(optr);
  					optr = NULL;	/* this is important */
--- 2451,2457 ----
  											 ObjectIdGetDatum(typeId),
  											 0, 0, 0);
  					if (!HeapTupleIsValid(typeTup))
! 						elog(ERROR, "2475: cache lookup failed for type %u", typeId);
  
  					Py_DECREF(optr);
  					optr = NULL;	/* this is important */
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to