On tis, 2009-05-26 at 16:07 -0700, Caleb Welton wrote:
> Patch for plpythonu
> 
> Primary motivation of the attached patch is to support handling bytea
> conversion allowing for embedded nulls, which in turn allows for
> supporting the marshal module.
> 
> Secondary motivation is slightly improved performance for conversion
> routines of basic datatypes that have simple mappings between
> postgres/python.
> 
> Primary design is to change the conversion routines from being based
> on cstrings to datums, eg:
>     PLyBool_FromString(const char *)  =>
> PLyBool_FromBool(PLyDatumToOb, Datum);

I have reworked this patch a bit and extended the plpython test suite
around it.  Current copy attached.

The remaining problem is that the patch loses domain checking on the
return types, because some paths no longer go through the data type's
input function.  I have marked these places as FIXME, and the regression
tests also contain a failing test case for this.

What's needed here, I think, is an API that takes a datum plus type
information and checks whether the datum is valid within the domain.  I
haven't found one that is exported, but maybe someone could give a tip.
diff --git a/src/pl/plpython/expected/plpython_record.out b/src/pl/plpython/expected/plpython_record.out
index 9e4645d..c8c4f9d 100644
--- a/src/pl/plpython/expected/plpython_record.out
+++ b/src/pl/plpython/expected/plpython_record.out
@@ -313,13 +313,15 @@ $$ LANGUAGE plpythonu;
 SELECT * FROM test_type_record_error1();
 ERROR:  key "second" not found in mapping
 HINT:  To return null in a column, add the value None to the mapping with the key named after the column.
-CONTEXT:  PL/Python function "test_type_record_error1"
+CONTEXT:  while creating return value
+PL/Python function "test_type_record_error1"
 CREATE FUNCTION test_type_record_error2() RETURNS type_record AS $$
     return [ 'first' ]
 $$ LANGUAGE plpythonu;
 SELECT * FROM test_type_record_error2();
 ERROR:  length of returned sequence did not match number of columns in row
-CONTEXT:  PL/Python function "test_type_record_error2"
+CONTEXT:  while creating return value
+PL/Python function "test_type_record_error2"
 CREATE FUNCTION test_type_record_error3() RETURNS type_record AS $$
     class type_record: pass
     type_record.first = 'first'
@@ -328,4 +330,5 @@ $$ LANGUAGE plpythonu;
 SELECT * FROM test_type_record_error3();
 ERROR:  attribute "second" does not exist in Python object
 HINT:  To return null in a column, let the returned object have an attribute named after column with value None.
-CONTEXT:  PL/Python function "test_type_record_error3"
+CONTEXT:  while creating return value
+PL/Python function "test_type_record_error3"
diff --git a/src/pl/plpython/expected/plpython_trigger.out b/src/pl/plpython/expected/plpython_trigger.out
index 7591404..dd08303 100644
--- a/src/pl/plpython/expected/plpython_trigger.out
+++ b/src/pl/plpython/expected/plpython_trigger.out
@@ -353,7 +353,8 @@ BEFORE UPDATE ON trigger_test
 FOR EACH ROW EXECUTE PROCEDURE stupid4();
 UPDATE trigger_test SET v = 'null' WHERE i = 0;
 ERROR:  TD["new"] deleted, cannot modify row
-CONTEXT:  PL/Python function "stupid4"
+CONTEXT:  while modifying trigger row
+PL/Python function "stupid4"
 DROP TRIGGER stupid_trigger4 ON trigger_test;
 -- TD not a dictionary
 CREATE FUNCTION stupid5() RETURNS trigger
@@ -366,7 +367,8 @@ BEFORE UPDATE ON trigger_test
 FOR EACH ROW EXECUTE PROCEDURE stupid5();
 UPDATE trigger_test SET v = 'null' WHERE i = 0;
 ERROR:  TD["new"] is not a dictionary
-CONTEXT:  PL/Python function "stupid5"
+CONTEXT:  while modifying trigger row
+PL/Python function "stupid5"
 DROP TRIGGER stupid_trigger5 ON trigger_test;
 -- TD not having string keys
 CREATE FUNCTION stupid6() RETURNS trigger
@@ -379,7 +381,8 @@ BEFORE UPDATE ON trigger_test
 FOR EACH ROW EXECUTE PROCEDURE stupid6();
 UPDATE trigger_test SET v = 'null' WHERE i = 0;
 ERROR:  TD["new"] dictionary key at ordinal position 0 is not a string
-CONTEXT:  PL/Python function "stupid6"
+CONTEXT:  while modifying trigger row
+PL/Python function "stupid6"
 DROP TRIGGER stupid_trigger6 ON trigger_test;
 -- TD keys not corresponding to row columns
 CREATE FUNCTION stupid7() RETURNS trigger
@@ -392,7 +395,8 @@ BEFORE UPDATE ON trigger_test
 FOR EACH ROW EXECUTE PROCEDURE stupid7();
 UPDATE trigger_test SET v = 'null' WHERE i = 0;
 ERROR:  key "a" found in TD["new"] does not exist as a column in the triggering row
-CONTEXT:  PL/Python function "stupid7"
+CONTEXT:  while modifying trigger row
+PL/Python function "stupid7"
 DROP TRIGGER stupid_trigger7 ON trigger_test;
 -- calling a trigger function directly
 SELECT stupid7();
diff --git a/src/pl/plpython/expected/plpython_types.out b/src/pl/plpython/expected/plpython_types.out
index 476f329..a03d0cc 100644
--- a/src/pl/plpython/expected/plpython_types.out
+++ b/src/pl/plpython/expected/plpython_types.out
@@ -278,7 +278,7 @@ plpy.info(x, type(x))
 return x
 $$ LANGUAGE plpythonu;
 SELECT * FROM test_type_conversion_bytea('hello world');
-INFO:  ('\\x68656c6c6f20776f726c64', <type 'str'>)
+INFO:  ('hello world', <type 'str'>)
 CONTEXT:  PL/Python function "test_type_conversion_bytea"
  test_type_conversion_bytea 
 ----------------------------
@@ -308,8 +308,8 @@ $$ LANGUAGE plpythonu;
    Python as a string in bytea-encoding, which Python doesn't understand. */
 SELECT test_type_unmarshal(x) FROM test_type_marshal() x;
    test_type_unmarshal    
---------------------------
- FAILED: bad marshal data
+---------------------
+ hello world
 (1 row)
 
 --
@@ -332,7 +332,8 @@ SELECT * FROM test_type_conversion_uint2(100::uint2, -50);
 INFO:  (100, <type 'int'>)
 CONTEXT:  PL/Python function "test_type_conversion_uint2"
 ERROR:  value for domain uint2 violates check constraint "uint2_check"
-CONTEXT:  PL/Python function "test_type_conversion_uint2"
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_uint2"
 SELECT * FROM test_type_conversion_uint2(null, 1);
 INFO:  (None, <type 'NoneType'>)
 CONTEXT:  PL/Python function "test_type_conversion_uint2"
@@ -341,13 +342,29 @@ CONTEXT:  PL/Python function "test_type_conversion_uint2"
                           1
 (1 row)
 
+CREATE DOMAIN nnint AS int CHECK (VALUE IS NOT NULL);
+CREATE FUNCTION test_type_conversion_nnint(x nnint, y int) RETURNS nnint AS $$
+return y
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_conversion_nnint(10, 20);
+ test_type_conversion_nnint 
+----------------------------
+                         20
+(1 row)
+
+SELECT * FROM test_type_conversion_nnint(null, 20);
+ERROR:  value for domain nnint violates check constraint "nnint_check"
+SELECT * FROM test_type_conversion_nnint(10, null);
+ERROR:  value for domain nnint violates check constraint "nnint_check"
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_nnint"
 CREATE DOMAIN bytea10 AS bytea CHECK (octet_length(VALUE) = 10 AND VALUE IS NOT NULL);
 CREATE FUNCTION test_type_conversion_bytea10(x bytea10, y bytea) RETURNS bytea10 AS $$
 plpy.info(x, type(x))
 return y
 $$ LANGUAGE plpythonu;
 SELECT * FROM test_type_conversion_bytea10('hello wold', 'hello wold');
-INFO:  ('\\x68656c6c6f20776f6c64', <type 'str'>)
+INFO:  ('hello wold', <type 'str'>)
 CONTEXT:  PL/Python function "test_type_conversion_bytea10"
  test_type_conversion_bytea10 
 ------------------------------
@@ -357,14 +374,15 @@ CONTEXT:  PL/Python function "test_type_conversion_bytea10"
 SELECT * FROM test_type_conversion_bytea10('hello world', 'hello wold');
 ERROR:  value for domain bytea10 violates check constraint "bytea10_check"
 SELECT * FROM test_type_conversion_bytea10('hello word', 'hello world');
-INFO:  ('\\x68656c6c6f20776f7264', <type 'str'>)
+INFO:  ('hello word', <type 'str'>)
 CONTEXT:  PL/Python function "test_type_conversion_bytea10"
 ERROR:  value for domain bytea10 violates check constraint "bytea10_check"
 CONTEXT:  PL/Python function "test_type_conversion_bytea10"
 SELECT * FROM test_type_conversion_bytea10(null, 'hello word');
 ERROR:  value for domain bytea10 violates check constraint "bytea10_check"
 SELECT * FROM test_type_conversion_bytea10('hello word', null);
-INFO:  ('\\x68656c6c6f20776f7264', <type 'str'>)
+INFO:  ('hello word', <type 'str'>)
 CONTEXT:  PL/Python function "test_type_conversion_bytea10"
 ERROR:  value for domain bytea10 violates check constraint "bytea10_check"
-CONTEXT:  PL/Python function "test_type_conversion_bytea10"
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_bytea10"
diff --git a/src/pl/plpython/expected/plpython_unicode.out b/src/pl/plpython/expected/plpython_unicode.out
index ce19eb9..d3b6fd1 100644
--- a/src/pl/plpython/expected/plpython_unicode.out
+++ b/src/pl/plpython/expected/plpython_unicode.out
@@ -24,13 +24,15 @@ rv = plpy.execute(plan, u"\\x80", 1)
 return rv[0]["testvalue1"]
 ' LANGUAGE plpythonu;
 SELECT unicode_return_error();
-ERROR:  PL/Python: could not create string representation of Python object, while creating return value
+ERROR:  PL/Python: could not create string representation of Python object
 DETAIL:  <type 'exceptions.UnicodeEncodeError'>: 'ascii' codec can't encode character u'\x80' in position 0: ordinal not in range(128)
-CONTEXT:  PL/Python function "unicode_return_error"
+CONTEXT:  while creating return value
+PL/Python function "unicode_return_error"
 INSERT INTO unicode_test (testvalue) VALUES ('test');
-ERROR:  PL/Python: could not compute string representation of Python object, while modifying trigger row
+ERROR:  PL/Python: could not create string representation of Python object
 DETAIL:  <type 'exceptions.UnicodeEncodeError'>: 'ascii' codec can't encode character u'\x80' in position 0: ordinal not in range(128)
-CONTEXT:  PL/Python function "unicode_trigger_error"
+CONTEXT:  while modifying trigger row
+PL/Python function "unicode_trigger_error"
 SELECT unicode_plan_error1();
 WARNING:  PL/Python: <class 'plpy.Error'>: unrecognized error in PLy_spi_execute_plan
 CONTEXT:  PL/Python function "unicode_plan_error1"
diff --git a/src/pl/plpython/expected/plpython_unicode_2.out b/src/pl/plpython/expected/plpython_unicode_2.out
index 9280fe7..7bb02c7 100644
--- a/src/pl/plpython/expected/plpython_unicode_2.out
+++ b/src/pl/plpython/expected/plpython_unicode_2.out
@@ -24,13 +24,15 @@ rv = plpy.execute(plan, u"\\x80", 1)
 return rv[0]["testvalue1"]
 ' LANGUAGE plpythonu;
 SELECT unicode_return_error();
-ERROR:  PL/Python: could not create string representation of Python object, while creating return value
+ERROR:  PL/Python: could not create string representation of Python object
 DETAIL:  exceptions.UnicodeError: ASCII encoding error: ordinal not in range(128)
-CONTEXT:  PL/Python function "unicode_return_error"
+CONTEXT:  while creating return value
+PL/Python function "unicode_return_error"
 INSERT INTO unicode_test (testvalue) VALUES ('test');
-ERROR:  PL/Python: could not compute string representation of Python object, while modifying trigger row
+ERROR:  PL/Python: could not compute string representation of Python object
 DETAIL:  exceptions.UnicodeError: ASCII encoding error: ordinal not in range(128)
-CONTEXT:  PL/Python function "unicode_trigger_error"
+CONTEXT:  while modifying trigger row
+PL/Python function "unicode_trigger_error"
 SELECT unicode_plan_error1();
 WARNING:  PL/Python: plpy.Error: unrecognized error in PLy_spi_execute_plan
 CONTEXT:  PL/Python function "unicode_plan_error1"
diff --git a/src/pl/plpython/expected/plpython_unicode_3.out b/src/pl/plpython/expected/plpython_unicode_3.out
index f058e2b..6395871 100644
--- a/src/pl/plpython/expected/plpython_unicode_3.out
+++ b/src/pl/plpython/expected/plpython_unicode_3.out
@@ -24,13 +24,15 @@ rv = plpy.execute(plan, u"\\x80", 1)
 return rv[0]["testvalue1"]
 ' LANGUAGE plpythonu;
 SELECT unicode_return_error();
-ERROR:  PL/Python: could not create string representation of Python object, while creating return value
+ERROR:  PL/Python: could not create string representation of Python object
 DETAIL:  exceptions.UnicodeEncodeError: 'ascii' codec can't encode character u'\x80' in position 0: ordinal not in range(128)
-CONTEXT:  PL/Python function "unicode_return_error"
+CONTEXT:  while creating return value
+PL/Python function "unicode_return_error"
 INSERT INTO unicode_test (testvalue) VALUES ('test');
-ERROR:  PL/Python: could not compute string representation of Python object, while modifying trigger row
+ERROR:  PL/Python: could not compute string representation of Python object
 DETAIL:  exceptions.UnicodeEncodeError: 'ascii' codec can't encode character u'\x80' in position 0: ordinal not in range(128)
-CONTEXT:  PL/Python function "unicode_trigger_error"
+CONTEXT:  while modifying trigger row
+PL/Python function "unicode_trigger_error"
 SELECT unicode_plan_error1();
 WARNING:  PL/Python: plpy.Error: unrecognized error in PLy_spi_execute_plan
 CONTEXT:  PL/Python function "unicode_plan_error1"
diff --git a/src/pl/plpython/expected/plpython_void.out b/src/pl/plpython/expected/plpython_void.out
index d067de0..1080d12 100644
--- a/src/pl/plpython/expected/plpython_void.out
+++ b/src/pl/plpython/expected/plpython_void.out
@@ -20,7 +20,8 @@ SELECT test_void_func1(), test_void_func1() IS NULL AS "is null";
 
 SELECT test_void_func2(); -- should fail
 ERROR:  PL/Python function with return type "void" did not return None
-CONTEXT:  PL/Python function "test_void_func2"
+CONTEXT:  while creating return value
+PL/Python function "test_void_func2"
 SELECT test_return_none(), test_return_none() IS NULL AS "is null";
  test_return_none | is null 
 ------------------+---------
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index cfc2225..cab5e45 100644
--- a/src/pl/plpython/plpython.c
+++ b/src/pl/plpython/plpython.c
@@ -78,7 +78,8 @@ PG_MODULE_MAGIC;
  * objects.
  */
 
-typedef PyObject *(*PLyDatumToObFunc) (const char *);
+struct PLyDatumToOb;
+typedef PyObject *(*PLyDatumToObFunc) (struct PLyDatumToOb*, Datum);
 
 typedef struct PLyDatumToOb
 {
@@ -104,8 +105,16 @@ typedef union PLyTypeInput
 /* convert PyObject to a Postgresql Datum or tuple.
  * output from Python
  */
+
+struct PLyObToDatum;
+struct PLyTypeInfo;
+typedef Datum (*PLyObToDatumFunc) (struct PLyTypeInfo*,
+								   struct PLyObToDatum*,
+								   PyObject *);
+
 typedef struct PLyObToDatum
 {
+	PLyObToDatumFunc func;
 	FmgrInfo	typfunc;		/* The type's input function */
 	Oid			typoid;			/* The OID of the type */
 	Oid			typioparam;
@@ -131,12 +140,11 @@ typedef struct PLyTypeInfo
 {
 	PLyTypeInput in;
 	PLyTypeOutput out;
-	int			is_rowtype;
-
 	/*
-	 * 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
+	 * 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
 	 */
+	int			is_rowtype;
 } PLyTypeInfo;
 
 
@@ -263,12 +271,26 @@ static void PLy_output_tuple_funcs(PLyTypeInfo *, TupleDesc);
 static void PLy_input_tuple_funcs(PLyTypeInfo *, TupleDesc);
 
 /* conversion functions */
+static PyObject *PLyBool_FromBool(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyFloat_FromFloat4(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyFloat_FromFloat8(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyFloat_FromNumeric(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyInt_FromInt16(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyInt_FromInt32(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyLong_FromInt64(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyString_FromText(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyString_FromDatum(PLyDatumToOb *arg, Datum d);
+
 static PyObject *PLyDict_FromTuple(PLyTypeInfo *, HeapTuple, TupleDesc);
-static PyObject *PLyBool_FromString(const char *);
-static PyObject *PLyFloat_FromString(const char *);
-static PyObject *PLyInt_FromString(const char *);
-static PyObject *PLyLong_FromString(const char *);
-static PyObject *PLyString_FromString(const char *);
+
+static Datum PLyObject_ToBool(PLyTypeInfo *, PLyObToDatum *,
+							  PyObject *);
+static Datum PLyObject_ToBytea(PLyTypeInfo *, PLyObToDatum *,
+							   PyObject *);
+static Datum PLyObject_ToText(PLyTypeInfo *, PLyObToDatum *,
+							  PyObject *);
+static Datum PLyObject_ToDatum(PLyTypeInfo *, PLyObToDatum *,
+							   PyObject *);
 
 static HeapTuple PLyMapping_ToTuple(PLyTypeInfo *, PyObject *);
 static HeapTuple PLySequence_ToTuple(PLyTypeInfo *, PyObject *);
@@ -339,6 +361,20 @@ plpython_error_callback(void *arg)
 		errcontext("PL/Python function \"%s\"", PLy_procedure_name(PLy_curr_procedure));
 }
 
+static void
+plpython_trigger_error_callback(void *arg)
+{
+	if (PLy_curr_procedure)
+		errcontext("while modifying trigger row");
+}
+
+static void
+plpython_return_error_callback(void *arg)
+{
+	if (PLy_curr_procedure)
+		errcontext("while creating return value");
+}
+
 Datum
 plpython_call_handler(PG_FUNCTION_ARGS)
 {
@@ -506,6 +542,11 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
 	Datum	   *volatile modvalues;
 	char	   *volatile modnulls;
 	TupleDesc	tupdesc;
+	ErrorContextCallback plerrcontext;
+
+	plerrcontext.callback = plpython_trigger_error_callback;
+	plerrcontext.previous = error_context_stack;
+	error_context_stack = &plerrcontext;
 
 	plntup = plkeys = platt = plval = plstr = NULL;
 	modattrs = NULL;
@@ -533,8 +574,6 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
 
 		for (i = 0; i < natts; i++)
 		{
-			char	   *src;
-
 			platt = PyList_GetItem(plkeys, i);
 			if (!PyString_Check(platt))
 				ereport(ERROR,
@@ -561,20 +600,9 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
 			}
 			else if (plval != Py_None)
 			{
-				plstr = PyObject_Str(plval);
-				if (!plstr)
-					PLy_elog(ERROR, "could not compute string representation of Python object, while modifying trigger row");
-				src = PyString_AsString(plstr);
-
-				modvalues[i] =
-					InputFunctionCall(&proc->result.out.r.atts[atti].typfunc,
-									  src,
-									proc->result.out.r.atts[atti].typioparam,
-									  tupdesc->attrs[atti]->atttypmod);
+				PLyObToDatum *att = &proc->result.out.r.atts[atti];
+				modvalues[i] = (att->func) (&proc->result, att, plval);
 				modnulls[i] = ' ';
-
-				Py_DECREF(plstr);
-				plstr = NULL;
 			}
 			else
 			{
@@ -620,6 +648,8 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
 	pfree(modvalues);
 	pfree(modnulls);
 
+	error_context_stack = plerrcontext.previous;
+
 	return rtup;
 }
 
@@ -809,8 +839,7 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure *proc)
 	Datum		rv;
 	PyObject   *volatile plargs = NULL;
 	PyObject   *volatile plrv = NULL;
-	PyObject   *volatile plrv_so = NULL;
-	char	   *plrv_sc;
+	ErrorContextCallback plerrcontext;
 
 	PG_TRY();
 	{
@@ -887,7 +916,6 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure *proc)
 
 				Py_XDECREF(plargs);
 				Py_XDECREF(plrv);
-				Py_XDECREF(plrv_so);
 
 				PLy_function_delete_args(proc);
 
@@ -901,6 +929,12 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure *proc)
 			}
 		}
 
+		/* Convert python return value into postgres datatypes */
+
+		plerrcontext.callback = plpython_return_error_callback;
+		plerrcontext.previous = error_context_stack;
+		error_context_stack = &plerrcontext;
+
 		/*
 		 * If the function is declared to return void, the Python return value
 		 * must be None. For void-returning functions, we also treat a None
@@ -957,21 +991,18 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure *proc)
 		else
 		{
 			fcinfo->isnull = false;
-			plrv_so = PyObject_Str(plrv);
-			if (!plrv_so)
-				PLy_elog(ERROR, "could not create string representation of Python object, while creating return value");
-			plrv_sc = PyString_AsString(plrv_so);
-			rv = InputFunctionCall(&proc->result.out.d.typfunc,
-								   plrv_sc,
-								   proc->result.out.d.typioparam,
-								   -1);
+			rv = (proc->result.out.d.func) (&proc->result,
+											&proc->result.out.d,
+											plrv);
+			// FIMXE: call input function for domain check
 		}
+
+		error_context_stack = plerrcontext.previous;
 	}
 	PG_CATCH();
 	{
 		Py_XDECREF(plargs);
 		Py_XDECREF(plrv);
-		Py_XDECREF(plrv_so);
 
 		PG_RE_THROW();
 	}
@@ -979,7 +1010,6 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure *proc)
 
 	Py_XDECREF(plargs);
 	Py_DECREF(plrv);
-	Py_XDECREF(plrv_so);
 
 	return rv;
 }
@@ -1062,12 +1092,8 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc)
 					arg = NULL;
 				else
 				{
-					char	   *ct;
-
-					ct = OutputFunctionCall(&(proc->args[i].in.d.typfunc),
-											fcinfo->arg[i]);
-					arg = (proc->args[i].in.d.func) (ct);
-					pfree(ct);
+					arg = (proc->args[i].in.d.func) (&(proc->args[i].in.d),
+													 fcinfo->arg[i]);
 				}
 			}
 
@@ -1618,6 +1644,33 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
 	arg->typoid = HeapTupleGetOid(typeTup);
 	arg->typioparam = getTypeIOParam(typeTup);
 	arg->typbyval = typeStruct->typbyval;
+
+	/* Determine which kind of Python object we will convert to */
+	switch (getBaseType(arg->typoid))
+	{
+		case BOOLOID:
+			arg->func = PLyObject_ToBool;
+			break;
+		case BYTEAOID:
+			arg->func = PLyObject_ToBytea;
+			break;
+		case BPCHAROID:
+		case VARCHAROID:
+		case TEXTOID:
+			arg->func = PLyObject_ToText;
+			break;
+
+		case FLOAT4OID:
+		case FLOAT8OID:
+		case NUMERICOID:
+		case INT2OID:
+		case INT4OID:
+		case INT8OID:
+		case VOIDOID:
+		default:
+			arg->func = PLyObject_ToDatum;
+			break;
+	}
 }
 
 static void
@@ -1644,22 +1697,34 @@ PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup)
 	switch (getBaseType(typeOid))
 	{
 		case BOOLOID:
-			arg->func = PLyBool_FromString;
+			arg->func = PLyBool_FromBool;
 			break;
 		case FLOAT4OID:
+			arg->func = PLyFloat_FromFloat4;
+			break;
 		case FLOAT8OID:
+			arg->func = PLyFloat_FromFloat8;
+			break;
 		case NUMERICOID:
-			arg->func = PLyFloat_FromString;
+			arg->func = PLyFloat_FromNumeric;
 			break;
 		case INT2OID:
+			arg->func = PLyInt_FromInt16;
+			break;
 		case INT4OID:
-			arg->func = PLyInt_FromString;
+			arg->func = PLyInt_FromInt32;
 			break;
 		case INT8OID:
-			arg->func = PLyLong_FromString;
+			arg->func = PLyLong_FromInt64;
+			break;
+		case BPCHAROID:
+		case VARCHAROID:
+		case TEXTOID:
+		case BYTEAOID:
+			arg->func = PLyString_FromText;
 			break;
 		default:
-			arg->func = PLyString_FromString;
+			arg->func = PLyString_FromDatum;
 			break;
 	}
 }
@@ -1685,9 +1750,8 @@ PLy_typeinfo_dealloc(PLyTypeInfo *arg)
 	}
 }
 
-/* assumes that a bool is always returned as a 't' or 'f' */
 static PyObject *
-PLyBool_FromString(const char *src)
+PLyBool_FromBool(PLyDatumToOb *arg, Datum d)
 {
 	/*
 	 * We would like to use Py_RETURN_TRUE and Py_RETURN_FALSE here for
@@ -1695,47 +1759,75 @@ PLyBool_FromString(const char *src)
 	 * Python >= 2.3, and we support older versions.
 	 * http://docs.python.org/api/boolObjects.html
 	 */
-	if (src[0] == 't')
+	if (DatumGetBool(d))
 		return PyBool_FromLong(1);
 	return PyBool_FromLong(0);
 }
 
 static PyObject *
-PLyFloat_FromString(const char *src)
+PLyFloat_FromFloat4(PLyDatumToOb *arg, Datum d)
 {
-	double		v;
-	char	   *eptr;
+	return PyFloat_FromDouble(DatumGetFloat4(d));
+}
 
-	errno = 0;
-	v = strtod(src, &eptr);
-	if (*eptr != '\0' || errno)
-		return NULL;
-	return PyFloat_FromDouble(v);
+static PyObject *
+PLyFloat_FromFloat8(PLyDatumToOb *arg, Datum d)
+{
+	return PyFloat_FromDouble(DatumGetFloat8(d));
 }
 
 static PyObject *
-PLyInt_FromString(const char *src)
+PLyFloat_FromNumeric(PLyDatumToOb *arg, Datum d)
 {
-	long		v;
-	char	   *eptr;
+	/*
+	 * Numeric is cast to a PyFloat:
+	 *   This results in a loss of precision
+	 *   Would it be better to cast to PyString?
+	 */
+	Datum  f = DirectFunctionCall1(numeric_float8, d);
+	double x = DatumGetFloat8(f);
+	return PyFloat_FromDouble(x);
+}
 
-	errno = 0;
-	v = strtol(src, &eptr, 0);
-	if (*eptr != '\0' || errno)
-		return NULL;
-	return PyInt_FromLong(v);
+static PyObject *
+PLyInt_FromInt16(PLyDatumToOb *arg, Datum d)
+{
+	return PyInt_FromLong(DatumGetInt16(d));
+}
+
+static PyObject *
+PLyInt_FromInt32(PLyDatumToOb *arg, Datum d)
+{
+	return PyInt_FromLong(DatumGetInt32(d));
+}
+
+static PyObject *
+PLyLong_FromInt64(PLyDatumToOb *arg, Datum d)
+{
+	/* on 32 bit platforms "long" may be too small */
+	if (sizeof(int64) > sizeof(long))
+		return PyLong_FromLongLong(DatumGetInt64(d));
+	else
+		return PyLong_FromLong(DatumGetInt64(d));
 }
 
 static PyObject *
-PLyLong_FromString(const char *src)
+PLyString_FromText(PLyDatumToOb *arg, Datum d)
 {
-	return PyLong_FromString((char *) src, NULL, 0);
+	text     *txt = DatumGetTextP(d);
+	char     *str = VARDATA(txt);
+	size_t    size = VARSIZE(txt) - VARHDRSZ;
+
+	return PyString_FromStringAndSize(str, size);
 }
 
 static PyObject *
-PLyString_FromString(const char *src)
+PLyString_FromDatum(PLyDatumToOb *arg, Datum d)
 {
-	return PyString_FromString(src);
+	char     *x = OutputFunctionCall(&arg->typfunc, d);
+	PyObject *r = PyString_FromString(x);
+	pfree(x);
+	return r;
 }
 
 static PyObject *
@@ -1755,8 +1847,7 @@ PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc)
 	{
 		for (i = 0; i < info->in.r.natts; i++)
 		{
-			char	   *key,
-					   *vsrc;
+			char	   *key;
 			Datum		vattr;
 			bool		is_null;
 			PyObject   *value;
@@ -1771,14 +1862,7 @@ PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc)
 				PyDict_SetItemString(dict, key, Py_None);
 			else
 			{
-				vsrc = OutputFunctionCall(&info->in.r.atts[i].typfunc,
-										  vattr);
-
-				/*
-				 * no exceptions allowed
-				 */
-				value = info->in.r.atts[i].func(vsrc);
-				pfree(vsrc);
+				value = (info->in.r.atts[i].func) (&info->in.r.atts[i], vattr);
 				PyDict_SetItemString(dict, key, value);
 				Py_DECREF(value);
 			}
@@ -1794,6 +1878,147 @@ PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc)
 	return dict;
 }
 
+static Datum
+PLyObject_ToBool(PLyTypeInfo *info,
+				 PLyObToDatum *arg,
+				 PyObject *plrv)
+{
+	Assert(plrv != Py_None);
+	return BoolGetDatum(PyObject_IsTrue(plrv));
+	// FIXME: domain check
+}
+
+
+static Datum
+PLyObject_ToBytea(PLyTypeInfo *info,
+				  PLyObToDatum *arg,
+				  PyObject *plrv)
+{
+	PyObject   *volatile plrv_so = NULL;
+	Datum       rv;
+
+	Assert(plrv != Py_None);
+
+	plrv_so = PyObject_Str(plrv);
+	if (!plrv_so)
+		PLy_elog(ERROR, "could not create string representation of Python object");
+
+	PG_TRY();
+	{
+		char *plrv_sc = PyString_AsString(plrv_so);
+		size_t len = PyString_Size(plrv_so);
+		size_t size = len + VARHDRSZ;
+		bytea *result = (bytea*) palloc(size);
+
+		SET_VARSIZE(result, size);
+		memcpy(VARDATA(result), plrv_sc, len);
+		rv = PointerGetDatum(result);
+	}
+	PG_CATCH();
+	{
+		Py_XDECREF(plrv_so);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	Py_XDECREF(plrv_so);
+
+	return rv;
+	// FIXME: domain check
+}
+
+static Datum
+PLyObject_ToText(PLyTypeInfo *info,
+				 PLyObToDatum *arg,
+				 PyObject *plrv)
+{
+	PyObject   *volatile plrv_so = NULL;
+	Datum       rv;
+
+	Assert(plrv != Py_None);
+
+	plrv_so = PyObject_Str(plrv);
+	if (!plrv_so)
+		PLy_elog(ERROR, "could not create string representation of Python object");
+
+	PG_TRY();
+	{
+		char *plrv_sc = PyString_AsString(plrv_so);
+		size_t len    = PyString_Size(plrv_so);
+		size_t size   = len + VARHDRSZ;
+		text *result;
+
+		if (strlen(plrv_sc) != (size_t) len)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("could not convert Python object into text: expected string without null bytes")));
+		}
+
+		result = (bytea*) palloc(size);
+		SET_VARSIZE(result, size);
+		memcpy(VARDATA(result), plrv_sc, len);
+		rv = PointerGetDatum(result);
+	}
+	PG_CATCH();
+	{
+		Py_XDECREF(plrv_so);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	Py_XDECREF(plrv_so);
+
+	return rv;
+	// FIXME: domain check
+}
+
+/*
+ * Generic conversion function:
+ *  - Cast PyObject to cstring and cstring into postgres type.
+ */
+static Datum
+PLyObject_ToDatum(PLyTypeInfo *info,
+				  PLyObToDatum *arg,
+				  PyObject *plrv)
+{
+	PyObject *volatile plrv_so = NULL;
+	Datum     rv;
+
+	Assert(plrv != Py_None);
+
+	plrv_so = PyObject_Str(plrv);
+	if (!plrv_so)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("could not create string representation of Python object")));
+	}
+
+	PG_TRY();
+	{
+		char *plrv_sc = PyString_AsString(plrv_so);
+		size_t len    = PyString_Size(plrv_so);
+
+		if (strlen(plrv_sc) != (size_t) len)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("could not convert Python object into cstring: expected string without null bytes")));
+		}
+		rv = InputFunctionCall(&arg->typfunc, plrv_sc, arg->typioparam, -1);
+	}
+	PG_CATCH();
+	{
+		Py_XDECREF(plrv_so);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	Py_XDECREF(plrv_so);
+
+	return rv;
+}
 
 static HeapTuple
 PLyMapping_ToTuple(PLyTypeInfo *info, PyObject *mapping)
@@ -1817,11 +2042,12 @@ PLyMapping_ToTuple(PLyTypeInfo *info, PyObject *mapping)
 	for (i = 0; i < desc->natts; ++i)
 	{
 		char	   *key;
-		PyObject   *volatile value,
-				   *volatile so;
+		PyObject   *volatile value;
+		PLyObToDatum *att;
 
 		key = NameStr(desc->attrs[i]->attname);
-		value = so = NULL;
+		value = NULL;
+		att = &info->out.r.atts[i];
 		PG_TRY();
 		{
 			value = PyMapping_GetItemString(mapping, key);
@@ -1832,19 +2058,7 @@ PLyMapping_ToTuple(PLyTypeInfo *info, PyObject *mapping)
 			}
 			else if (value)
 			{
-				char	   *valuestr;
-
-				so = PyObject_Str(value);
-				if (so == NULL)
-					PLy_elog(ERROR, "could not compute string representation of Python object");
-				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;
+				values[i] = (att->func) (info, att, value);
 				nulls[i] = false;
 			}
 			else
@@ -1859,7 +2073,6 @@ PLyMapping_ToTuple(PLyTypeInfo *info, PyObject *mapping)
 		}
 		PG_CATCH();
 		{
-			Py_XDECREF(so);
 			Py_XDECREF(value);
 			PG_RE_THROW();
 		}
@@ -1906,10 +2119,11 @@ PLySequence_ToTuple(PLyTypeInfo *info, PyObject *sequence)
 	nulls = palloc(sizeof(bool) * desc->natts);
 	for (i = 0; i < desc->natts; ++i)
 	{
-		PyObject   *volatile value,
-				   *volatile so;
+		PyObject   *volatile value;
+		PLyObToDatum *att;
 
-		value = so = NULL;
+		value = NULL;
+		att = &info->out.r.atts[i];
 		PG_TRY();
 		{
 			value = PySequence_GetItem(sequence, i);
@@ -1921,18 +2135,7 @@ PLySequence_ToTuple(PLyTypeInfo *info, PyObject *sequence)
 			}
 			else if (value)
 			{
-				char	   *valuestr;
-
-				so = PyObject_Str(value);
-				if (so == NULL)
-					PLy_elog(ERROR, "could not compute string representation of Python object");
-				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;
+				values[i] = (att->func) (info, att, value);
 				nulls[i] = false;
 			}
 
@@ -1941,7 +2144,6 @@ PLySequence_ToTuple(PLyTypeInfo *info, PyObject *sequence)
 		}
 		PG_CATCH();
 		{
-			Py_XDECREF(so);
 			Py_XDECREF(value);
 			PG_RE_THROW();
 		}
@@ -1977,11 +2179,12 @@ PLyObject_ToTuple(PLyTypeInfo *info, PyObject *object)
 	for (i = 0; i < desc->natts; ++i)
 	{
 		char	   *key;
-		PyObject   *volatile value,
-				   *volatile so;
+		PyObject   *volatile value;
+		PLyObToDatum *att;
 
 		key = NameStr(desc->attrs[i]->attname);
-		value = so = NULL;
+		value = NULL;
+		att = &info->out.r.atts[i];
 		PG_TRY();
 		{
 			value = PyObject_GetAttrString(object, key);
@@ -1992,18 +2195,7 @@ PLyObject_ToTuple(PLyTypeInfo *info, PyObject *object)
 			}
 			else if (value)
 			{
-				char	   *valuestr;
-
-				so = PyObject_Str(value);
-				if (so == NULL)
-					PLy_elog(ERROR, "could not compute string representation of Python object");
-				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;
+				values[i] = (att->func) (info, att, value);
 				nulls[i] = false;
 			}
 			else
@@ -2019,7 +2211,6 @@ PLyObject_ToTuple(PLyTypeInfo *info, PyObject *object)
 		}
 		PG_CATCH();
 		{
-			Py_XDECREF(so);
 			Py_XDECREF(value);
 			PG_RE_THROW();
 		}
diff --git a/src/pl/plpython/sql/plpython_types.sql b/src/pl/plpython/sql/plpython_types.sql
index 79fbbb9..49c15c2 100644
--- a/src/pl/plpython/sql/plpython_types.sql
+++ b/src/pl/plpython/sql/plpython_types.sql
@@ -142,6 +142,17 @@ SELECT * FROM test_type_conversion_uint2(100::uint2, -50);
 SELECT * FROM test_type_conversion_uint2(null, 1);
 
 
+CREATE DOMAIN nnint AS int CHECK (VALUE IS NOT NULL);
+
+CREATE FUNCTION test_type_conversion_nnint(x nnint, y int) RETURNS nnint AS $$
+return y
+$$ LANGUAGE plpythonu;
+
+SELECT * FROM test_type_conversion_nnint(10, 20);
+SELECT * FROM test_type_conversion_nnint(null, 20);
+SELECT * FROM test_type_conversion_nnint(10, null);
+
+
 CREATE DOMAIN bytea10 AS bytea CHECK (octet_length(VALUE) = 10 AND VALUE IS NOT NULL);
 
 CREATE FUNCTION test_type_conversion_bytea10(x bytea10, y bytea) RETURNS bytea10 AS $$
-- 
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