1) named parameters additionally to args[]
2) return composite-types from plpython as dictionary
3) return result-set from plpython as list, iterator or generator

Test script attached (patch-test.sql) but not integrated to plpython test-suite.


--
Sven Suursoho

Attachment: patch-test.sql
Description: Binary data

Attachment: functions.sql
Description: Binary data

*** postgresql-8.0.7-orig/src/pl/plpython/plpython.c	Tue Jan 17 19:33:37 2006
--- postgresql-8.0.7/src/pl/plpython/plpython.c	Sat Apr 15 16:16:44 2006
***************
*** 47,52 ****
--- 47,53 ----
  #include "catalog/pg_type.h"
  #include "commands/trigger.h"
  #include "executor/spi.h"
+ #include "funcapi.h"
  #include "fmgr.h"
  #include "nodes/makefuncs.h"
  #include "parser/parse_type.h"
***************
*** 99,104 ****
--- 100,106 ----
  {
  	PLyObToDatum *atts;
  	int			natts;
+ 	Oid typoid;	/* Tuple type oid */
  }	PLyObToTuple;
  
  typedef union PLyTypeOutput
***************
*** 119,124 ****
--- 121,127 ----
  	/*
  	 * is_rowtype can be: -1  not known yet (initial state) 0  scalar
  	 * datatype 1  rowtype 2  rowtype, but I/O functions not set up yet
+ 	 * 3 procedure return composite type I/O is set up
  	 */
  }	PLyTypeInfo;
  
***************
*** 134,139 ****
--- 137,147 ----
  	bool		fn_readonly;
  	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. */
+ 	int	    setof_count;	/* numbef of items to return in result set */
+ 	int	    setof_current;	/* current item in result set */
+ 	char	    **argnames;		/* Argument names */
  	PLyTypeInfo args[FUNC_MAX_ARGS];
  	int			nargs;
  	PyObject   *code;			/* compiled procedure code */
***************
*** 216,221 ****
--- 224,230 ----
  static HeapTuple PLy_trigger_handler(FunctionCallInfo fcinfo, PLyProcedure *);
  
  static PyObject *PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *);
+ static void PLy_function_delete_args(PLyProcedure *);
  static PyObject *PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *,
  					   HeapTuple *);
  static HeapTuple PLy_modify_tuple(PLyProcedure *, PyObject *,
***************
*** 251,256 ****
--- 260,266 ----
  static PyObject *PLyInt_FromString(const char *);
  static PyObject *PLyLong_FromString(const char *);
  static PyObject *PLyString_FromString(const char *);
+ static HeapTuple PLyDict_ToTuple(PLyTypeInfo *, PyObject *);
  
  
  /* global data
***************
*** 748,758 ****
  
  	PG_TRY();
  	{
! 		plargs = PLy_function_build_args(fcinfo, proc);
! 		plrv = PLy_procedure_call(proc, "args", plargs);
! 
! 		Assert(plrv != NULL);
! 		Assert(!PLy_error_in_progress);
  
  		/*
  		 * Disconnect from SPI manager and then create the return values
--- 758,774 ----
  
  	PG_TRY();
  	{
! 		if (!proc->is_setof || proc->setof_count == -1)
! 		{
! 			/* python function not called yet, do it */
! 			plargs = PLy_function_build_args(fcinfo, proc);
! 			plrv = PLy_procedure_call(proc, "args", plargs);
! 			if (!proc->is_setof)
! 				/* SETOF function parameters are deleted when called last row is returned */
! 				PLy_function_delete_args(proc);
! 			Assert(plrv != NULL);
! 			Assert(!PLy_error_in_progress);
! 		}
  
  		/*
  		 * Disconnect from SPI manager and then create the return values
***************
*** 763,768 ****
--- 779,854 ----
  		if (SPI_finish() != SPI_OK_FINISH)
  			elog(ERROR, "SPI_finish failed");
  
+ 		if (proc->is_setof)
+ 		{
+ 			bool is_done = false;
+ 			ReturnSetInfo *rsi = (ReturnSetInfo *)fcinfo->resultinfo;
+ 
+ 			if (proc->setof_current == -1)
+ 			{
+ 				/* first time -- do checks and setup */
+ 				if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+ 						(rsi->allowedModes & SFRM_ValuePerCall) == 0)
+ 				{
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("only value per call is allowed")));
+ 				}
+ 				rsi->returnMode = SFRM_ValuePerCall;
+ 
+ 				/* fetch information about returned object */
+ 				proc->setof = plrv;
+ 				plrv = NULL;
+ 				if (PyList_Check(proc->setof))
+ 					/* SETOF as list */
+ 					proc->setof_count = PyList_GET_SIZE(proc->setof);
+ 				else if (PyIter_Check(proc->setof))
+ 					/* SETOF as iterator, unknown number of items */
+ 					proc->setof_current = proc->setof_count = 0;
+ 				else
+ 				{
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+ 							 errmsg("SETOF must be returned as list or iterator")));
+ 				}
+ 			}
+ 
+ 			Assert(proc->setof != NULL);
+ 
+ 			/* Fetch next of SETOF */
+ 			if (PyList_Check(proc->setof))
+ 			{
+ 				is_done = ++proc->setof_current == proc->setof_count;
+ 				if (!is_done)
+ 					plrv = PyList_GET_ITEM(proc->setof, proc->setof_current);
+ 			}
+ 			else if (PyIter_Check(proc->setof))
+ 			{
+ 				plrv = PyIter_Next(proc->setof);
+ 				is_done = plrv == NULL;
+ 			}
+ 
+ 			if (!is_done)
+ 			{
+ 				rsi->isDone = ExprMultipleResult;
+ 			}
+ 			else
+ 			{
+ 				rsi->isDone = ExprEndResult;
+ 				proc->setof_count = proc->setof_current = -1;
+ 				Py_DECREF(proc->setof);
+ 				proc->setof = NULL;
+ 
+ 				Py_XDECREF(plargs);
+ 				Py_XDECREF(plrv);
+ 				Py_XDECREF(plrv_so);
+ 
+ 				PLy_function_delete_args(proc);
+ 				fcinfo->isnull = true;
+ 				return (Datum)NULL;
+ 			}
+ 		}
+ 
  		/*
  		 * convert the python PyObject to a postgresql Datum
  		 */
***************
*** 771,776 ****
--- 857,882 ----
  			fcinfo->isnull = true;
  			rv = (Datum) NULL;
  		}
+ 		else if (proc->result.is_rowtype >= 2)
+ 		{
+ 			HeapTuple   tuple;
+ 
+ 			/* returning composite type */
+ 			if (!PyDict_Check(plrv))
+ 				elog(ERROR, "tuple must be returned as dictionary");
+ 
+ 			tuple = PLyDict_ToTuple(&proc->result, plrv);
+ 			if (tuple != NULL)
+ 			{
+ 				fcinfo->isnull = false;
+ 				rv = HeapTupleGetDatum(tuple);
+ 			}
+ 			else
+ 			{
+ 				fcinfo->isnull = true;
+ 				rv = (Datum) NULL;
+ 			}
+ 		}
  		else
  		{
  			fcinfo->isnull = false;
***************
*** 904,909 ****
--- 1010,1016 ----
  			 * FIXME -- error check this
  			 */
  			PyList_SetItem(args, i, arg);
+ 			PyDict_SetItemString(proc->globals, proc->argnames[i], arg);
  			arg = NULL;
  		}
  	}
***************
*** 920,925 ****
--- 1027,1042 ----
  }
  
  
+ static void
+ PLy_function_delete_args(PLyProcedure *proc)
+ {
+ 	int	i;
+ 
+ 	for (i = 0; i < proc->nargs; i++)
+ 		PyDict_DelItemString(proc->globals, proc->argnames[i]);
+ }
+ 
+ 
  /*
   * PLyProcedure functions
   */
***************
*** 990,995 ****
--- 1107,1115 ----
  	bool		isnull;
  	int			i,
  				rv;
+ 	Datum		argnames;
+ 	Datum	    	*elems;
+ 	int		nelems;
  
  	procStruct = (Form_pg_proc) GETSTRUCT(procTup);
  
***************
*** 1023,1028 ****
--- 1143,1152 ----
  	proc->nargs = 0;
  	proc->code = proc->statics = NULL;
  	proc->globals = proc->me = NULL;
+ 	proc->is_setof = procStruct->proretset;
+ 	proc->setof = NULL;
+ 	proc->setof_count = proc->setof_current = -1;
+ 	proc->argnames = NULL;
  
  	PG_TRY();
  	{
***************
*** 1058,1066 ****
  			}
  
  			if (rvTypeStruct->typtype == 'c')
! 				ereport(ERROR,
! 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					errmsg("plpython functions cannot return tuples yet")));
  			else
  				PLy_output_datum_func(&proc->result, rvTypeTup);
  
--- 1182,1192 ----
  			}
  
  			if (rvTypeStruct->typtype == 'c')
! 			{
! 				/* Tuple: set up later, during first call to PLy_function_handler */
! 				proc->result.out.r.typoid = procStruct->prorettype;
! 				proc->result.is_rowtype = 2;
! 			}
  			else
  				PLy_output_datum_func(&proc->result, rvTypeTup);
  
***************
*** 1080,1088 ****
  
  		/*
  		 * now get information required for input conversion of the
! 		 * procedures arguments.
  		 */
  		proc->nargs = fcinfo->nargs;
  		for (i = 0; i < fcinfo->nargs; i++)
  		{
  			HeapTuple	argTypeTup;
--- 1206,1229 ----
  
  		/*
  		 * now get information required for input conversion of the
! 		 * procedure's arguments.
  		 */
  		proc->nargs = fcinfo->nargs;
+ 		proc->argnames = NULL;
+ 		if (proc->nargs)
+ 		{
+ 			argnames = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_proargnames, &isnull);
+ 			if (!isnull)
+ 			{
+ 				deconstruct_array(DatumGetArrayTypeP(argnames), TEXTOID, -1, false, 'i',
+ 						&elems, &nelems);
+ 				if (nelems != proc->nargs)
+ 					elog(ERROR,
+ 							"proargnames must have the same number of elements "
+ 							"as the function has arguments");
+ 				proc->argnames = (char **) PLy_malloc(sizeof(char *)*proc->nargs);
+ 			}
+ 		}
  		for (i = 0; i < fcinfo->nargs; i++)
  		{
  			HeapTuple	argTypeTup;
***************
*** 1112,1119 ****
  												 * funcs */
  
  			ReleaseSysCache(argTypeTup);
- 		}
  
  
  		/*
  		 * get the text of the function.
--- 1253,1269 ----
  												 * funcs */
  
  			ReleaseSysCache(argTypeTup);
  
+ 			/* Fetch argument name */
+ 			if (proc->argnames)
+ 			{
+ 				const char  *name;
+ 
+ 				name = DatumGetCString(DirectFunctionCall1(textout, elems[i]));
+ 				proc->argnames[i] = PLy_malloc(strlen(name) + 1);
+ 				strcpy(proc->argnames[i], name);
+ 			}
+ 		}
  
  		/*
  		 * get the text of the function.
***************
*** 1249,1254 ****
--- 1399,1405 ----
  	if (proc->pyname)
  		PLy_free(proc->pyname);
  	for (i = 0; i < proc->nargs; i++)
+ 	{
  		if (proc->args[i].is_rowtype == 1)
  		{
  			if (proc->args[i].in.r.atts)
***************
*** 1256,1261 ****
--- 1407,1417 ----
  			if (proc->args[i].out.r.atts)
  				PLy_free(proc->args[i].out.r.atts);
  		}
+ 		if (proc->argnames && proc->argnames[i])
+ 			PLy_free(proc->argnames[i]);
+ 	}
+ 	if (proc->argnames)
+ 		PLy_free(proc->argnames);
  }
  
  /* conversion functions.  remember output from python is
***************
*** 1518,1523 ****
--- 1674,1752 ----
  	return dict;
  }
  
+ 
+ static HeapTuple
+ PLyDict_ToTuple(PLyTypeInfo *info, PyObject *dict)
+ {
+ 	TupleDesc	desc;
+ 	HeapTuple	tuple;
+ 	Datum		*values;
+ 	char		*nulls;
+ 	int		i;
+ 
+ 	desc = CreateTupleDescCopy(lookup_rowtype_tupdesc(info->out.r.typoid, -1));
+ 
+ 	/* Set up tuple type, if neccessary */
+ 	if (info->is_rowtype == 2)
+ 	{
+ 		PLy_output_tuple_funcs(info, desc);
+ 		info->is_rowtype = 3;
+ 	}
+ 	Assert(info->is_rowtype == 3);
+ 
+ 	/* Build tuple */
+ 	values = palloc(sizeof(Datum)*desc->natts);
+ 	nulls = palloc(sizeof(char)*desc->natts);
+ 	for (i = 0;  i < desc->natts;  ++i)
+ 	{
+ 		char		*key;
+ 		PyObject	*value,
+ 				*so;
+ 
+ 		key = NameStr(desc->attrs[i]->attname);
+ 		value = so = NULL;
+ 		PG_TRY();
+ 		{
+ 			value = PyDict_GetItemString(dict, key);
+ 			if (value != Py_None && value != NULL)
+ 			{
+ 				char *valuestr;
+ 
+ 				so = PyObject_Str(value);
+ 				valuestr = PyString_AsString(so);
+ 				values[i] = FunctionCall3(&info->out.r.atts[i].typfunc
+ 						, CStringGetDatum(valuestr)
+ 						, ObjectIdGetDatum(info->out.r.atts[i].typioparam)
+ 						, Int32GetDatum(-1));
+ 				Py_DECREF(so);
+ 				value = so = NULL;
+ 				nulls[i] = ' ';
+ 			}
+ 			else
+ 			{
+ 				value = NULL;
+ 				values[i] = (Datum) NULL;
+ 				nulls[i] = 'n';
+ 			}
+ 		}
+ 		PG_CATCH();
+ 		{
+ 			Py_XDECREF(value);
+ 			Py_XDECREF(so);
+ 			PG_RE_THROW();
+ 		}
+ 		PG_END_TRY();
+ 	}
+ 
+ 	tuple = heap_formtuple(desc, values, nulls);
+ 	FreeTupleDesc(desc);
+ 	pfree(values);
+ 	pfree(nulls);
+ 
+ 	return tuple;
+ }
+ 
+ 
  /* initialization, some python variables function declared here
   */
  
***************
*** 2651,2653 ****
--- 2880,2883 ----
  {
  	free(ptr);
  }
+ /* vim: set noexpandtab nosmarttab shiftwidth=8 cinoptions=l1j1: */
---------------------------(end of broadcast)---------------------------
TIP 9: In versions below 8.0, the planner will ignore your desire to
       choose an index scan if your joining column's datatypes do not
       match

Reply via email to