diff --git a/src/pl/plpython/expected/plpython_types.out b/src/pl/plpython/expected/plpython_types.out
index eda965a..dc1232b 100644
--- a/src/pl/plpython/expected/plpython_types.out
+++ b/src/pl/plpython/expected/plpython_types.out
@@ -134,6 +134,158 @@ INFO:  (None, <type 'NoneType'>)
                           
 (1 row)
 
+CREATE FUNCTION test_type_conversion_int4_int2(x int4) RETURNS int2 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_conversion_int4_int2(100::int4);
+INFO:  (100, <type 'int'>)
+ test_type_conversion_int4_int2 
+--------------------------------
+                            100
+(1 row)
+
+SELECT * FROM test_type_conversion_int4_int2(-100::int4);
+INFO:  (-100, <type 'int'>)
+ test_type_conversion_int4_int2 
+--------------------------------
+                           -100
+(1 row)
+
+SELECT * FROM test_type_conversion_int4_int2(1000000::int4);
+INFO:  (1000000, <type 'int'>)
+ERROR:  value "1000000" is out of range for type smallint
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_int4_int2"
+SELECT * FROM test_type_conversion_int4_int2(-1000000::int4);
+INFO:  (-1000000, <type 'int'>)
+ERROR:  value "-1000000" is out of range for type smallint
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_int4_int2"
+SELECT * FROM test_type_conversion_int4_int2(null);
+INFO:  (None, <type 'NoneType'>)
+ test_type_conversion_int4_int2 
+--------------------------------
+                               
+(1 row)
+
+CREATE FUNCTION test_type_conversion_int8_int2(x int8) RETURNS int2 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_conversion_int8_int2(100::int8);
+INFO:  (100L, <type 'long'>)
+ test_type_conversion_int8_int2 
+--------------------------------
+                            100
+(1 row)
+
+SELECT * FROM test_type_conversion_int8_int2(-100::int8);
+INFO:  (-100L, <type 'long'>)
+ test_type_conversion_int8_int2 
+--------------------------------
+                           -100
+(1 row)
+
+SELECT * FROM test_type_conversion_int8_int2(1000000::int8);
+INFO:  (1000000L, <type 'long'>)
+ERROR:  value "1000000" is out of range for type smallint
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_int8_int2"
+SELECT * FROM test_type_conversion_int8_int2(-1000000::int8);
+INFO:  (-1000000L, <type 'long'>)
+ERROR:  value "-1000000" is out of range for type smallint
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_int8_int2"
+SELECT * FROM test_type_conversion_int8_int2(null);
+INFO:  (None, <type 'NoneType'>)
+ test_type_conversion_int8_int2 
+--------------------------------
+                               
+(1 row)
+
+CREATE FUNCTION test_type_conversion_float8_int2(x float8) RETURNS int2 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_conversion_float8_int2(100::float8);
+INFO:  (100.0, <type 'float'>)
+ test_type_conversion_float8_int2 
+----------------------------------
+                              100
+(1 row)
+
+SELECT * FROM test_type_conversion_float8_int2(-100::float8);
+INFO:  (-100.0, <type 'float'>)
+ test_type_conversion_float8_int2 
+----------------------------------
+                             -100
+(1 row)
+
+SELECT * FROM test_type_conversion_float8_int2(100.345::float8);
+INFO:  (100.345, <type 'float'>)
+ test_type_conversion_float8_int2 
+----------------------------------
+                              100
+(1 row)
+
+SELECT * FROM test_type_conversion_float8_int2(-100.678::float8);
+INFO:  (-100.678, <type 'float'>)
+ test_type_conversion_float8_int2 
+----------------------------------
+                             -100
+(1 row)
+
+SELECT * FROM test_type_conversion_float8_int2(1000000::float8);
+INFO:  (1000000.0, <type 'float'>)
+ERROR:  value "1000000.0" is out of range for type smallint
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_float8_int2"
+SELECT * FROM test_type_conversion_float8_int2(-1000000::float8);
+INFO:  (-1000000.0, <type 'float'>)
+ERROR:  value "-1000000.0" is out of range for type smallint
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_float8_int2"
+SELECT * FROM test_type_conversion_float8_int2(null);
+INFO:  (None, <type 'NoneType'>)
+ test_type_conversion_float8_int2 
+----------------------------------
+                                 
+(1 row)
+
+CREATE FUNCTION test_type_conversion_text_int2(x text) RETURNS int2 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_conversion_text_int2('100');
+INFO:  ('100', <type 'str'>)
+ test_type_conversion_text_int2 
+--------------------------------
+                            100
+(1 row)
+
+SELECT * FROM test_type_conversion_text_int2('-100');
+INFO:  ('-100', <type 'str'>)
+ test_type_conversion_text_int2 
+--------------------------------
+                           -100
+(1 row)
+
+SELECT * FROM test_type_conversion_text_int2('1000000000000000');
+INFO:  ('1000000000000000', <type 'str'>)
+ERROR:  value "1000000000000000" is out of range for type smallint
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_text_int2"
+SELECT * FROM test_type_conversion_text_int2('1.23');
+INFO:  ('1.23', <type 'str'>)
+ERROR:  invalid input syntax for integer: "1.23"
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_text_int2"
+SELECT * FROM test_type_conversion_text_int2('aaa');
+INFO:  ('aaa', <type 'str'>)
+ERROR:  invalid input syntax for integer: "aaa"
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_text_int2"
 CREATE FUNCTION test_type_conversion_int4(x int4) RETURNS int4 AS $$
 plpy.info(x, type(x))
 return x
@@ -159,6 +311,132 @@ INFO:  (None, <type 'NoneType'>)
                           
 (1 row)
 
+CREATE FUNCTION test_type_conversion_int8_int4(x int8) RETURNS int4 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_conversion_int8_int4(100);
+INFO:  (100L, <type 'long'>)
+ test_type_conversion_int8_int4 
+--------------------------------
+                            100
+(1 row)
+
+SELECT * FROM test_type_conversion_int8_int4(-100);
+INFO:  (-100L, <type 'long'>)
+ test_type_conversion_int8_int4 
+--------------------------------
+                           -100
+(1 row)
+
+SELECT * FROM test_type_conversion_int8_int4(10000000000000);
+INFO:  (10000000000000L, <type 'long'>)
+ERROR:  value "10000000000000" is out of range for type integer
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_int8_int4"
+SELECT * FROM test_type_conversion_int8_int4(-10000000000000);
+INFO:  (-10000000000000L, <type 'long'>)
+ERROR:  value "-10000000000000" is out of range for type integer
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_int8_int4"
+SELECT * FROM test_type_conversion_int8_int4(null);
+INFO:  (None, <type 'NoneType'>)
+ test_type_conversion_int8_int4 
+--------------------------------
+                               
+(1 row)
+
+CREATE FUNCTION test_type_conversion_numeric_int4(x numeric) RETURNS int4 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_conversion_numeric_int4(100::numeric);
+INFO:  (Decimal('100'), <class 'decimal.Decimal'>)
+ test_type_conversion_numeric_int4 
+-----------------------------------
+                               100
+(1 row)
+
+SELECT * FROM test_type_conversion_numeric_int4(-100::numeric);
+INFO:  (Decimal('-100'), <class 'decimal.Decimal'>)
+ test_type_conversion_numeric_int4 
+-----------------------------------
+                              -100
+(1 row)
+
+SELECT * FROM test_type_conversion_numeric_int4(1000000::numeric);
+INFO:  (Decimal('1000000'), <class 'decimal.Decimal'>)
+ test_type_conversion_numeric_int4 
+-----------------------------------
+                           1000000
+(1 row)
+
+SELECT * FROM test_type_conversion_numeric_int4(-1000000::numeric);
+INFO:  (Decimal('-1000000'), <class 'decimal.Decimal'>)
+ test_type_conversion_numeric_int4 
+-----------------------------------
+                          -1000000
+(1 row)
+
+SELECT * FROM test_type_conversion_numeric_int4(100000000000000000000::numeric);
+INFO:  (Decimal('100000000000000000000'), <class 'decimal.Decimal'>)
+ERROR:  value "100000000000000000000" is out of range for type integer
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_numeric_int4"
+SELECT * FROM test_type_conversion_numeric_int4(-100000000000000000000::numeric);
+INFO:  (Decimal('-100000000000000000000'), <class 'decimal.Decimal'>)
+ERROR:  value "-100000000000000000000" is out of range for type integer
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_numeric_int4"
+SELECT * FROM test_type_conversion_numeric_int4(null);
+INFO:  (None, <type 'NoneType'>)
+ test_type_conversion_numeric_int4 
+-----------------------------------
+                                  
+(1 row)
+
+CREATE FUNCTION test_type_conversion_long_int4(x numeric) RETURNS int4 AS $$
+plpy.info(x, type(x))
+return long(x)
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_conversion_long_int4(100::numeric);
+INFO:  (Decimal('100'), <class 'decimal.Decimal'>)
+ test_type_conversion_long_int4 
+--------------------------------
+                            100
+(1 row)
+
+SELECT * FROM test_type_conversion_long_int4(-100::numeric);
+INFO:  (Decimal('-100'), <class 'decimal.Decimal'>)
+ test_type_conversion_long_int4 
+--------------------------------
+                           -100
+(1 row)
+
+SELECT * FROM test_type_conversion_long_int4(1000000::numeric);
+INFO:  (Decimal('1000000'), <class 'decimal.Decimal'>)
+ test_type_conversion_long_int4 
+--------------------------------
+                        1000000
+(1 row)
+
+SELECT * FROM test_type_conversion_long_int4(-1000000::numeric);
+INFO:  (Decimal('-1000000'), <class 'decimal.Decimal'>)
+ test_type_conversion_long_int4 
+--------------------------------
+                       -1000000
+(1 row)
+
+SELECT * FROM test_type_conversion_long_int4(100000000000000000000::numeric);
+INFO:  (Decimal('100000000000000000000'), <class 'decimal.Decimal'>)
+ERROR:  value "100000000000000000000" is out of range for type integer
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_long_int4"
+SELECT * FROM test_type_conversion_long_int4(-100000000000000000000::numeric);
+INFO:  (Decimal('-100000000000000000000'), <class 'decimal.Decimal'>)
+ERROR:  value "-100000000000000000000" is out of range for type integer
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_long_int4"
 CREATE FUNCTION test_type_conversion_int8(x int8) RETURNS int8 AS $$
 plpy.info(x, type(x))
 return x
@@ -191,6 +469,104 @@ INFO:  (None, <type 'NoneType'>)
                           
 (1 row)
 
+CREATE FUNCTION test_type_conversion_float8_int8(x float8) RETURNS int8 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_conversion_float8_int8(100::float8);
+INFO:  (100.0, <type 'float'>)
+ test_type_conversion_float8_int8 
+----------------------------------
+                              100
+(1 row)
+
+SELECT * FROM test_type_conversion_float8_int8(-100::float8);
+INFO:  (-100.0, <type 'float'>)
+ test_type_conversion_float8_int8 
+----------------------------------
+                             -100
+(1 row)
+
+SELECT * FROM test_type_conversion_float8_int8(100.345::float8);
+INFO:  (100.345, <type 'float'>)
+ test_type_conversion_float8_int8 
+----------------------------------
+                              100
+(1 row)
+
+SELECT * FROM test_type_conversion_float8_int8(-100.678::float8);
+INFO:  (-100.678, <type 'float'>)
+ test_type_conversion_float8_int8 
+----------------------------------
+                             -100
+(1 row)
+
+SELECT * FROM test_type_conversion_float8_int8(100000000000.345::float8);
+INFO:  (100000000000.345, <type 'float'>)
+ test_type_conversion_float8_int8 
+----------------------------------
+                     100000000000
+(1 row)
+
+SELECT * FROM test_type_conversion_float8_int8(-100000000000.678::float8);
+INFO:  (-100000000000.678, <type 'float'>)
+ test_type_conversion_float8_int8 
+----------------------------------
+                    -100000000000
+(1 row)
+
+SELECT * FROM test_type_conversion_float8_int8(100000000000000000000000::float8);
+INFO:  (1e+23, <type 'float'>)
+ERROR:  value "1e+23" is out of range for type bigint
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_float8_int8"
+SELECT * FROM test_type_conversion_float8_int8(-100000000000000000000000::float8);
+INFO:  (-1e+23, <type 'float'>)
+ERROR:  value "-1e+23" is out of range for type bigint
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_float8_int8"
+SELECT * FROM test_type_conversion_float8_int8(null);
+INFO:  (None, <type 'NoneType'>)
+ test_type_conversion_float8_int8 
+----------------------------------
+                                 
+(1 row)
+
+CREATE FUNCTION test_type_conversion_text_int8(x text) RETURNS int8 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_conversion_text_int8('100');
+INFO:  ('100', <type 'str'>)
+ test_type_conversion_text_int8 
+--------------------------------
+                            100
+(1 row)
+
+SELECT * FROM test_type_conversion_text_int8('-100');
+INFO:  ('-100', <type 'str'>)
+ test_type_conversion_text_int8 
+--------------------------------
+                           -100
+(1 row)
+
+SELECT * FROM test_type_conversion_text_int8('1000000000000000');
+INFO:  ('1000000000000000', <type 'str'>)
+ test_type_conversion_text_int8 
+--------------------------------
+               1000000000000000
+(1 row)
+
+SELECT * FROM test_type_conversion_text_int8('100000000000000000000000');
+INFO:  ('100000000000000000000000', <type 'str'>)
+ERROR:  value "100000000000000000000000" is out of range for type bigint
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_text_int8"
+SELECT * FROM test_type_conversion_text_int8('aaa');
+INFO:  ('aaa', <type 'str'>)
+ERROR:  invalid input syntax for integer: "aaa"
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_text_int8"
 CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$
 # print just the class name, not the type, to avoid differences
 # between decimal and cdecimal
@@ -253,6 +629,31 @@ INFO:  ('None', 'NoneType')
                              
 (1 row)
 
+CREATE FUNCTION test_type_conversion_int4_numeric(x int4) RETURNS numeric AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_conversion_int4_numeric(100::int4);
+INFO:  (100, <type 'int'>)
+ test_type_conversion_int4_numeric 
+-----------------------------------
+                               100
+(1 row)
+
+SELECT * FROM test_type_conversion_int4_numeric(-100::int4);
+INFO:  (-100, <type 'int'>)
+ test_type_conversion_int4_numeric 
+-----------------------------------
+                              -100
+(1 row)
+
+SELECT * FROM test_type_conversion_int4_numeric(null);
+INFO:  (None, <type 'NoneType'>)
+ test_type_conversion_int4_numeric 
+-----------------------------------
+                                  
+(1 row)
+
 CREATE FUNCTION test_type_conversion_float4(x float4) RETURNS float4 AS $$
 plpy.info(x, type(x))
 return x
@@ -285,6 +686,43 @@ INFO:  (None, <type 'NoneType'>)
                             
 (1 row)
 
+CREATE FUNCTION test_type_conversion_text_float4(x text) RETURNS float4 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_conversion_text_float4('100');
+INFO:  ('100', <type 'str'>)
+ test_type_conversion_text_float4 
+----------------------------------
+                              100
+(1 row)
+
+SELECT * FROM test_type_conversion_text_float4('-100');
+INFO:  ('-100', <type 'str'>)
+ test_type_conversion_text_float4 
+----------------------------------
+                             -100
+(1 row)
+
+SELECT * FROM test_type_conversion_text_float4('5000.5');
+INFO:  ('5000.5', <type 'str'>)
+ test_type_conversion_text_float4 
+----------------------------------
+                           5000.5
+(1 row)
+
+SELECT * FROM test_type_conversion_text_float4('aaa');
+INFO:  ('aaa', <type 'str'>)
+ERROR:  invalid input syntax for type real: "aaa"
+CONTEXT:  while creating return value
+PL/Python function "test_type_conversion_text_float4"
+SELECT * FROM test_type_conversion_text_float4(null);
+INFO:  (None, <type 'NoneType'>)
+ test_type_conversion_text_float4 
+----------------------------------
+                                 
+(1 row)
+
 CREATE FUNCTION test_type_conversion_float8(x float8) RETURNS float8 AS $$
 plpy.info(x, type(x))
 return x
@@ -324,6 +762,171 @@ INFO:  (100100100.654321, <type 'float'>)
             100100100.654321
 (1 row)
 
+CREATE FUNCTION test_type_conversion_text_float8(x text) RETURNS float8 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_conversion_text_float8('100');
+INFO:  ('100', <type 'str'>)
+ test_type_conversion_text_float8 
+----------------------------------
+                              100
+(1 row)
+
+SELECT * FROM test_type_conversion_text_float8('-100');
+INFO:  ('-100', <type 'str'>)
+ test_type_conversion_text_float8 
+----------------------------------
+                             -100
+(1 row)
+
+SELECT * FROM test_type_conversion_text_float8('5000000000.5');
+INFO:  ('5000000000.5', <type 'str'>)
+ test_type_conversion_text_float8 
+----------------------------------
+                     5000000000.5
+(1 row)
+
+SELECT * FROM test_type_conversion_text_float8('100100100.654321');
+INFO:  ('100100100.654321', <type 'str'>)
+ test_type_conversion_text_float8 
+----------------------------------
+                 100100100.654321
+(1 row)
+
+SELECT * FROM test_type_conversion_text_float8(null);
+INFO:  (None, <type 'NoneType'>)
+ test_type_conversion_text_float8 
+----------------------------------
+                                 
+(1 row)
+
+CREATE FUNCTION test_type_conversion_int4_float8(x int4) RETURNS float8 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_conversion_int4_float8(100);
+INFO:  (100, <type 'int'>)
+ test_type_conversion_int4_float8 
+----------------------------------
+                              100
+(1 row)
+
+SELECT * FROM test_type_conversion_int4_float8(-100);
+INFO:  (-100, <type 'int'>)
+ test_type_conversion_int4_float8 
+----------------------------------
+                             -100
+(1 row)
+
+SELECT * FROM test_type_conversion_int4_float8(null);
+INFO:  (None, <type 'NoneType'>)
+ test_type_conversion_int4_float8 
+----------------------------------
+                                 
+(1 row)
+
+CREATE FUNCTION test_type_conversion_int8_float8(x int8) RETURNS float8 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_conversion_int8_float8(100::int8);
+INFO:  (100L, <type 'long'>)
+ test_type_conversion_int8_float8 
+----------------------------------
+                              100
+(1 row)
+
+SELECT * FROM test_type_conversion_int8_float8(-100::int8);
+INFO:  (-100L, <type 'long'>)
+ test_type_conversion_int8_float8 
+----------------------------------
+                             -100
+(1 row)
+
+SELECT * FROM test_type_conversion_int8_float8(10000000000000::int8);
+INFO:  (10000000000000L, <type 'long'>)
+ test_type_conversion_int8_float8 
+----------------------------------
+                   10000000000000
+(1 row)
+
+SELECT * FROM test_type_conversion_int8_float8(-10000000000000::int8);
+INFO:  (-10000000000000L, <type 'long'>)
+ test_type_conversion_int8_float8 
+----------------------------------
+                  -10000000000000
+(1 row)
+
+SELECT * FROM test_type_conversion_int8_float8(null);
+INFO:  (None, <type 'NoneType'>)
+ test_type_conversion_int8_float8 
+----------------------------------
+                                 
+(1 row)
+
+CREATE FUNCTION test_type_conversion_numeric_float8(x numeric) RETURNS float8 AS $$
+# print just the class name, not the type, to avoid differences
+# between decimal and cdecimal
+plpy.info(str(x), x.__class__.__name__)
+return x
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_conversion_numeric_float8(100);
+INFO:  ('100', 'Decimal')
+ test_type_conversion_numeric_float8 
+-------------------------------------
+                                 100
+(1 row)
+
+SELECT * FROM test_type_conversion_numeric_float8(-100);
+INFO:  ('-100', 'Decimal')
+ test_type_conversion_numeric_float8 
+-------------------------------------
+                                -100
+(1 row)
+
+SELECT * FROM test_type_conversion_numeric_float8(100.0);
+INFO:  ('100.0', 'Decimal')
+ test_type_conversion_numeric_float8 
+-------------------------------------
+                                 100
+(1 row)
+
+SELECT * FROM test_type_conversion_numeric_float8(100.00);
+INFO:  ('100.00', 'Decimal')
+ test_type_conversion_numeric_float8 
+-------------------------------------
+                                 100
+(1 row)
+
+SELECT * FROM test_type_conversion_numeric_float8(5000000000.5);
+INFO:  ('5000000000.5', 'Decimal')
+ test_type_conversion_numeric_float8 
+-------------------------------------
+                        5000000000.5
+(1 row)
+
+SELECT * FROM test_type_conversion_numeric_float8(1234567890.0987654321);
+INFO:  ('1234567890.0987654321', 'Decimal')
+ test_type_conversion_numeric_float8 
+-------------------------------------
+                    1234567890.09877
+(1 row)
+
+SELECT * FROM test_type_conversion_numeric_float8(-1234567890.0987654321);
+INFO:  ('-1234567890.0987654321', 'Decimal')
+ test_type_conversion_numeric_float8 
+-------------------------------------
+                   -1234567890.09877
+(1 row)
+
+SELECT * FROM test_type_conversion_numeric_float8(null);
+INFO:  ('None', 'NoneType')
+ test_type_conversion_numeric_float8 
+-------------------------------------
+                                    
+(1 row)
+
 CREATE FUNCTION test_type_conversion_oid(x oid) RETURNS oid AS $$
 plpy.info(x, type(x))
 return x
diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c
index d6a6a84..8eeefa6 100644
--- a/src/pl/plpython/plpy_typeio.c
+++ b/src/pl/plpython/plpy_typeio.c
@@ -46,6 +46,18 @@ static PyObject *PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc
 /* conversion from Python objects to Datums */
 static Datum PLyObject_ToBool(PLyObToDatum *arg, PyObject *plrv,
 				 bool *isnull, bool inarray);
+static Datum PLyObject_ToInt16(PLyObToDatum *arg, PyObject *plrv,
+				  bool *isnull, bool inarray);
+static Datum PLyObject_ToInt32(PLyObToDatum *arg, PyObject *plrv,
+				  bool *isnull, bool inarray);
+static Datum PLyObject_ToInt64(PLyObToDatum *arg, PyObject *plrv,
+				  bool *isnull, bool inarray);
+static Datum PLyObject_ToFloat4(PLyObToDatum *arg, PyObject *plrv,
+				   bool *isnull, bool inarray);
+static Datum PLyObject_ToFloat8(PLyObToDatum *arg, PyObject *plrv,
+				   bool *isnull, bool inarray);
+static Datum PLyObject_ToNumeric(PLyObToDatum *arg, PyObject *plrv,
+					bool *isnull, bool inarray);
 static Datum PLyObject_ToBytea(PLyObToDatum *arg, PyObject *plrv,
 				  bool *isnull, bool inarray);
 static Datum PLyObject_ToComposite(PLyObToDatum *arg, PyObject *plrv,
@@ -397,16 +409,35 @@ PLy_output_setup_func(PLyObToDatum *arg, MemoryContext arg_mcxt,
 		{
 			case BOOLOID:
 				arg->func = PLyObject_ToBool;
-				break;
+				return; /* no need to initialize arg->u.scalar */
 			case BYTEAOID:
 				arg->func = PLyObject_ToBytea;
+				return;	/* no need to initialize arg->u.scalar */
+			case INT2OID:
+				arg->func = PLyObject_ToInt16;
+				break;
+			case INT4OID:
+				arg->func = PLyObject_ToInt32;
+				break;
+			case INT8OID:
+				arg->func = PLyObject_ToInt64;
+				break;
+			case FLOAT4OID:
+				arg->func = PLyObject_ToFloat4;
+				break;
+			case FLOAT8OID:
+				arg->func = PLyObject_ToFloat8;
+				break;
+			case NUMERICOID:
+				arg->func = PLyObject_ToNumeric;
 				break;
 			default:
 				arg->func = PLyObject_ToScalar;
-				getTypeInputInfo(typeOid, &typinput, &arg->u.scalar.typioparam);
-				fmgr_info_cxt(typinput, &arg->u.scalar.typfunc, arg_mcxt);
 				break;
 		}
+
+		getTypeInputInfo(typeOid, &typinput, &arg->u.scalar.typioparam);
+		fmgr_info_cxt(typinput, &arg->u.scalar.typfunc, arg_mcxt);
 	}
 }
 
@@ -884,6 +915,340 @@ PLyObject_ToBool(PLyObToDatum *arg, PyObject *plrv,
 	return BoolGetDatum(PyObject_IsTrue(plrv));
 }
 
+static bool
+PLyNumber_AsDouble(PyObject *plrv, double *res)
+{
+	if (PyFloat_Check(plrv))
+	{
+		*res = PyFloat_AS_DOUBLE(plrv);
+		return true;
+	}
+	else if (PyInt_Check(plrv))
+	{
+		long		l = PyInt_AsLong(plrv);
+
+		if (l != -1 || !PyErr_Occurred())
+		{
+			*res = (double) l;
+			return true;
+		}
+	}
+	else if (PyLong_Check(plrv))
+	{
+		*res = PyLong_AsDouble(plrv);
+
+		if (*res != -1 || !PyErr_Occurred())
+			return true;
+	}
+	else if (PyNumber_Check(plrv))
+	{
+		PyObject   *f = PyNumber_Float(plrv);
+
+		if (f)
+		{
+			*res = PyFloat_AS_DOUBLE(f);
+			Py_DECREF(f);
+			return true;
+		}
+	}
+	else
+		return false;
+
+	PyErr_Clear();
+	return false;
+}
+
+/*
+ * Try to convert Python exact integer number to C long.
+ * Returns true if the conversion was successful, *overflow is set if the value
+ * overflows long type.
+ */
+static bool
+PLyNumber_AsLong(PyObject *plrv, long *val, bool *overflow)
+{
+	if (PyInt_Check(plrv))
+		*val = PyInt_AsLong(plrv);
+	else if (PyLong_Check(plrv))
+		*val = PyLong_AsLong(plrv);
+	else
+		return false;
+
+	/* If -1 is returned then OverflowError is possible. */
+	*overflow = *val == -1 && PyErr_Occurred();
+
+	if (*overflow)
+	{
+		/* Catch OverflowError exception. */
+		if (!PyErr_ExceptionMatches(PyExc_OverflowError))
+			PLy_elog(ERROR, NULL);
+
+		PyErr_Clear();
+	}
+
+	return true;
+}
+
+static bool
+PLyNumber_ToLong(PyObject *plrv, bool try_float, long *res, bool *overflow,
+				 PyObject **pintval)
+{
+	PyObject   *intval;
+
+	if (PLyNumber_AsLong(plrv, res, overflow))
+		return true;
+
+	if (!PyNumber_Check(plrv))
+		return false;	/* not a number */
+
+	/* Try to convert Python float to int. */
+	if (try_float && PyFloat_Check(plrv))
+	{
+		double		dblval = PyFloat_AS_DOUBLE(plrv);
+
+		*overflow = dblval < LONG_MIN || dblval > LONG_MAX;
+
+		if (!*overflow)
+			*res = (long) dblval;
+
+		return true;
+	}
+
+	/* Try to convert a number to Python int/long type to round its value. */
+	intval = PyNumber_Int(plrv);
+
+	if (intval)
+	{
+		bool		converted = PLyNumber_AsLong(intval, res, overflow);
+
+		if (pintval)
+			*pintval = intval;
+		else
+			Py_DECREF(intval);
+
+		return converted;
+	}
+	else if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_OverflowError))
+	{
+		PyErr_Clear();
+		*overflow = true;
+		return true;
+	}
+	else
+	{
+		PyErr_Clear();
+		return false;
+	}
+}
+
+/*
+ * Convert a Python object to a PostgreSQL int16 datum directly.
+ * If can not convert it directly, fallback to PLyObject_ToScalar
+ * to convert it.
+ */
+static Datum
+PLyObject_ToInt16(PLyObToDatum *arg, PyObject *plrv,
+				  bool *isnull, bool inarray)
+{
+	long		val;
+	bool		overflow;
+
+	if (plrv == Py_None)
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	if (PLyNumber_ToLong(plrv, true, &val, &overflow, NULL))
+	{
+		if (overflow || val < SHRT_MIN || val > SHRT_MAX)
+			ereport(ERROR,
+					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+					 errmsg("value \"%s\" is out of range for type %s",
+							PLyObject_AsString(plrv), "smallint")));
+
+		*isnull = false;
+		return Int16GetDatum((int16) val);
+	}
+
+	return PLyObject_ToScalar(arg, plrv, isnull, inarray);
+}
+
+/*
+ * Convert a Python object to a PostgreSQL int32 datum directly.
+ * If can not convert it directly, fallback to PLyObject_ToScalar
+ * to convert it.
+ */
+static Datum
+PLyObject_ToInt32(PLyObToDatum *arg, PyObject *plrv,
+				  bool *isnull, bool inarray)
+{
+	long		val;
+	bool		overflow;
+
+	if (plrv == Py_None)
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	if (PLyNumber_ToLong(plrv, true, &val, &overflow, NULL))
+	{
+		if (overflow ||
+#ifdef HAVE_LONG_INT_64
+			(val < PG_INT32_MIN || val > PG_INT32_MAX)
+#endif
+		)
+			ereport(ERROR,
+					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+					 errmsg("value \"%s\" is out of range for type %s",
+							 PLyObject_AsString(plrv), "integer")));
+
+		*isnull = false;
+		return Int32GetDatum((int32) val);
+	}
+
+	return PLyObject_ToScalar(arg, plrv, isnull, inarray);
+}
+
+/*
+ * Convert a Python object to a PostgreSQL int64 datum directly.
+ * If can not convert it directly, fallback to PLyObject_ToScalar
+ * to convert it.
+ */
+static Datum
+PLyObject_ToInt64(PLyObToDatum *arg, PyObject *plrv,
+				  bool *isnull, bool inarray)
+{
+	PyObject   *ival = NULL;
+	Datum		result;
+	long		lval;
+	bool		overflow;
+
+	if (plrv == Py_None)
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	/* 64-bit long can't be represented by a double without loss of precision */
+	if (PLyNumber_ToLong(plrv,
+#ifdef HAVE_LONG_INT_64
+						 false,
+#else
+						 true,
+#endif
+						 &lval, &overflow, &ival))
+	{
+		if (!overflow)
+		{
+			Py_XDECREF(ival);
+			*isnull = false;
+			return Int64GetDatum((int64) lval);
+		}
+
+#ifdef HAVE_LONG_INT_64
+		ereport(ERROR,
+					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+					 errmsg("value \"%s\" is out of range for type %s",
+							 PLyObject_AsString(plrv), "bigint")));
+#else
+		/* try to convert via I/O */
+#endif
+	}
+
+	/* Use integer value instead of original */
+	result = PLyObject_ToScalar(arg, ival ? ival : plrv, isnull, inarray);
+
+	Py_XDECREF(ival);
+
+	return result;
+}
+
+/*
+ * Convert a Python object to a PostgreSQL float4 datum directly.
+ * If can not convert it directly, fallback to PLyObject_ToScalar
+ * to convert it.
+ */
+static Datum
+PLyObject_ToFloat4(PLyObToDatum *arg, PyObject *plrv,
+				   bool *isnull, bool inarray)
+{
+	double		res;
+
+	if (plrv == Py_None)
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	if (PLyNumber_AsDouble(plrv, &res))
+	{
+		*isnull = false;
+		return Float4GetDatum((float4) res);
+	}
+
+	return PLyObject_ToScalar(arg, plrv, isnull, inarray);
+}
+
+/*
+ * Convert a Python object to a PostgreSQL float8 datum directly.
+ * If can not convert it directly, fallback to PLyObject_ToScalar
+ * to convert it.
+ */
+static Datum
+PLyObject_ToFloat8(PLyObToDatum *arg, PyObject *plrv,
+				   bool *isnull, bool inarray)
+{
+	double		res;
+
+	if (plrv == Py_None)
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	if (PLyNumber_AsDouble(plrv, &res))
+	{
+		*isnull = false;
+		return Float8GetDatum((float8) res);
+	}
+
+	return PLyObject_ToScalar(arg, plrv, isnull, inarray);
+}
+
+/*
+ * Convert a Python object to a PostgreSQL numeric datum directly.
+ * If can not convert it directly, fallback to PLyObject_ToScalar
+ * to convert it.
+ */
+static Datum
+PLyObject_ToNumeric(PLyObToDatum *arg, PyObject *plrv,
+					bool *isnull, bool inarray)
+{
+	long		val;
+	bool		overflow;
+
+	if (plrv == Py_None)
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	if (PLyNumber_AsLong(plrv, &val, &overflow))
+	{
+		if (!overflow)
+		{
+			*isnull = false;
+			return DirectFunctionCall1(int8_numeric,
+									   Int64GetDatum((int64) val));
+		}
+
+		/* try to convert via I/O */
+	}
+
+	return PLyObject_ToScalar(arg, plrv, isnull, inarray);
+}
+
 /*
  * Convert a Python object to a PostgreSQL bytea datum.  This doesn't
  * go through the generic conversion function to circumvent problems
diff --git a/src/pl/plpython/sql/plpython_types.sql b/src/pl/plpython/sql/plpython_types.sql
index cc0524e..446a4dd 100644
--- a/src/pl/plpython/sql/plpython_types.sql
+++ b/src/pl/plpython/sql/plpython_types.sql
@@ -63,6 +63,52 @@ SELECT * FROM test_type_conversion_int2(100::int2);
 SELECT * FROM test_type_conversion_int2(-100::int2);
 SELECT * FROM test_type_conversion_int2(null);
 
+CREATE FUNCTION test_type_conversion_int4_int2(x int4) RETURNS int2 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+
+SELECT * FROM test_type_conversion_int4_int2(100::int4);
+SELECT * FROM test_type_conversion_int4_int2(-100::int4);
+SELECT * FROM test_type_conversion_int4_int2(1000000::int4);
+SELECT * FROM test_type_conversion_int4_int2(-1000000::int4);
+SELECT * FROM test_type_conversion_int4_int2(null);
+
+CREATE FUNCTION test_type_conversion_int8_int2(x int8) RETURNS int2 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+
+SELECT * FROM test_type_conversion_int8_int2(100::int8);
+SELECT * FROM test_type_conversion_int8_int2(-100::int8);
+SELECT * FROM test_type_conversion_int8_int2(1000000::int8);
+SELECT * FROM test_type_conversion_int8_int2(-1000000::int8);
+SELECT * FROM test_type_conversion_int8_int2(null);
+
+CREATE FUNCTION test_type_conversion_float8_int2(x float8) RETURNS int2 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+
+SELECT * FROM test_type_conversion_float8_int2(100::float8);
+SELECT * FROM test_type_conversion_float8_int2(-100::float8);
+SELECT * FROM test_type_conversion_float8_int2(100.345::float8);
+SELECT * FROM test_type_conversion_float8_int2(-100.678::float8);
+SELECT * FROM test_type_conversion_float8_int2(1000000::float8);
+SELECT * FROM test_type_conversion_float8_int2(-1000000::float8);
+SELECT * FROM test_type_conversion_float8_int2(null);
+
+CREATE FUNCTION test_type_conversion_text_int2(x text) RETURNS int2 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+
+SELECT * FROM test_type_conversion_text_int2('100');
+SELECT * FROM test_type_conversion_text_int2('-100');
+SELECT * FROM test_type_conversion_text_int2('1000000000000000');
+SELECT * FROM test_type_conversion_text_int2('1.23');
+SELECT * FROM test_type_conversion_text_int2('aaa');
+
 
 CREATE FUNCTION test_type_conversion_int4(x int4) RETURNS int4 AS $$
 plpy.info(x, type(x))
@@ -74,6 +120,43 @@ SELECT * FROM test_type_conversion_int4(-100);
 SELECT * FROM test_type_conversion_int4(null);
 
 
+CREATE FUNCTION test_type_conversion_int8_int4(x int8) RETURNS int4 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+
+SELECT * FROM test_type_conversion_int8_int4(100);
+SELECT * FROM test_type_conversion_int8_int4(-100);
+SELECT * FROM test_type_conversion_int8_int4(10000000000000);
+SELECT * FROM test_type_conversion_int8_int4(-10000000000000);
+SELECT * FROM test_type_conversion_int8_int4(null);
+
+CREATE FUNCTION test_type_conversion_numeric_int4(x numeric) RETURNS int4 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+
+SELECT * FROM test_type_conversion_numeric_int4(100::numeric);
+SELECT * FROM test_type_conversion_numeric_int4(-100::numeric);
+SELECT * FROM test_type_conversion_numeric_int4(1000000::numeric);
+SELECT * FROM test_type_conversion_numeric_int4(-1000000::numeric);
+SELECT * FROM test_type_conversion_numeric_int4(100000000000000000000::numeric);
+SELECT * FROM test_type_conversion_numeric_int4(-100000000000000000000::numeric);
+SELECT * FROM test_type_conversion_numeric_int4(null);
+
+CREATE FUNCTION test_type_conversion_long_int4(x numeric) RETURNS int4 AS $$
+plpy.info(x, type(x))
+return long(x)
+$$ LANGUAGE plpythonu;
+
+SELECT * FROM test_type_conversion_long_int4(100::numeric);
+SELECT * FROM test_type_conversion_long_int4(-100::numeric);
+SELECT * FROM test_type_conversion_long_int4(1000000::numeric);
+SELECT * FROM test_type_conversion_long_int4(-1000000::numeric);
+SELECT * FROM test_type_conversion_long_int4(100000000000000000000::numeric);
+SELECT * FROM test_type_conversion_long_int4(-100000000000000000000::numeric);
+
+
 CREATE FUNCTION test_type_conversion_int8(x int8) RETURNS int8 AS $$
 plpy.info(x, type(x))
 return x
@@ -84,6 +167,31 @@ SELECT * FROM test_type_conversion_int8(-100);
 SELECT * FROM test_type_conversion_int8(5000000000);
 SELECT * FROM test_type_conversion_int8(null);
 
+CREATE FUNCTION test_type_conversion_float8_int8(x float8) RETURNS int8 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+
+SELECT * FROM test_type_conversion_float8_int8(100::float8);
+SELECT * FROM test_type_conversion_float8_int8(-100::float8);
+SELECT * FROM test_type_conversion_float8_int8(100.345::float8);
+SELECT * FROM test_type_conversion_float8_int8(-100.678::float8);
+SELECT * FROM test_type_conversion_float8_int8(100000000000.345::float8);
+SELECT * FROM test_type_conversion_float8_int8(-100000000000.678::float8);
+SELECT * FROM test_type_conversion_float8_int8(100000000000000000000000::float8);
+SELECT * FROM test_type_conversion_float8_int8(-100000000000000000000000::float8);
+SELECT * FROM test_type_conversion_float8_int8(null);
+
+CREATE FUNCTION test_type_conversion_text_int8(x text) RETURNS int8 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+
+SELECT * FROM test_type_conversion_text_int8('100');
+SELECT * FROM test_type_conversion_text_int8('-100');
+SELECT * FROM test_type_conversion_text_int8('1000000000000000');
+SELECT * FROM test_type_conversion_text_int8('100000000000000000000000');
+SELECT * FROM test_type_conversion_text_int8('aaa');
 
 CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$
 # print just the class name, not the type, to avoid differences
@@ -101,6 +209,15 @@ SELECT * FROM test_type_conversion_numeric(1234567890.0987654321);
 SELECT * FROM test_type_conversion_numeric(-1234567890.0987654321);
 SELECT * FROM test_type_conversion_numeric(null);
 
+CREATE FUNCTION test_type_conversion_int4_numeric(x int4) RETURNS numeric AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+
+SELECT * FROM test_type_conversion_int4_numeric(100::int4);
+SELECT * FROM test_type_conversion_int4_numeric(-100::int4);
+SELECT * FROM test_type_conversion_int4_numeric(null);
+
 
 CREATE FUNCTION test_type_conversion_float4(x float4) RETURNS float4 AS $$
 plpy.info(x, type(x))
@@ -112,6 +229,17 @@ SELECT * FROM test_type_conversion_float4(-100);
 SELECT * FROM test_type_conversion_float4(5000.5);
 SELECT * FROM test_type_conversion_float4(null);
 
+CREATE FUNCTION test_type_conversion_text_float4(x text) RETURNS float4 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+
+SELECT * FROM test_type_conversion_text_float4('100');
+SELECT * FROM test_type_conversion_text_float4('-100');
+SELECT * FROM test_type_conversion_text_float4('5000.5');
+SELECT * FROM test_type_conversion_text_float4('aaa');
+SELECT * FROM test_type_conversion_text_float4(null);
+
 
 CREATE FUNCTION test_type_conversion_float8(x float8) RETURNS float8 AS $$
 plpy.info(x, type(x))
@@ -124,6 +252,53 @@ SELECT * FROM test_type_conversion_float8(5000000000.5);
 SELECT * FROM test_type_conversion_float8(null);
 SELECT * FROM test_type_conversion_float8(100100100.654321);
 
+CREATE FUNCTION test_type_conversion_text_float8(x text) RETURNS float8 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+
+SELECT * FROM test_type_conversion_text_float8('100');
+SELECT * FROM test_type_conversion_text_float8('-100');
+SELECT * FROM test_type_conversion_text_float8('5000000000.5');
+SELECT * FROM test_type_conversion_text_float8('100100100.654321');
+SELECT * FROM test_type_conversion_text_float8(null);
+
+CREATE FUNCTION test_type_conversion_int4_float8(x int4) RETURNS float8 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+
+SELECT * FROM test_type_conversion_int4_float8(100);
+SELECT * FROM test_type_conversion_int4_float8(-100);
+SELECT * FROM test_type_conversion_int4_float8(null);
+
+CREATE FUNCTION test_type_conversion_int8_float8(x int8) RETURNS float8 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpythonu;
+
+SELECT * FROM test_type_conversion_int8_float8(100::int8);
+SELECT * FROM test_type_conversion_int8_float8(-100::int8);
+SELECT * FROM test_type_conversion_int8_float8(10000000000000::int8);
+SELECT * FROM test_type_conversion_int8_float8(-10000000000000::int8);
+SELECT * FROM test_type_conversion_int8_float8(null);
+
+CREATE FUNCTION test_type_conversion_numeric_float8(x numeric) RETURNS float8 AS $$
+# print just the class name, not the type, to avoid differences
+# between decimal and cdecimal
+plpy.info(str(x), x.__class__.__name__)
+return x
+$$ LANGUAGE plpythonu;
+
+SELECT * FROM test_type_conversion_numeric_float8(100);
+SELECT * FROM test_type_conversion_numeric_float8(-100);
+SELECT * FROM test_type_conversion_numeric_float8(100.0);
+SELECT * FROM test_type_conversion_numeric_float8(100.00);
+SELECT * FROM test_type_conversion_numeric_float8(5000000000.5);
+SELECT * FROM test_type_conversion_numeric_float8(1234567890.0987654321);
+SELECT * FROM test_type_conversion_numeric_float8(-1234567890.0987654321);
+SELECT * FROM test_type_conversion_numeric_float8(null);
+
 
 CREATE FUNCTION test_type_conversion_oid(x oid) RETURNS oid AS $$
 plpy.info(x, type(x))
