diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
index aaf758d..7e55b75 100644
--- a/doc/src/sgml/plpython.sgml
+++ b/doc/src/sgml/plpython.sgml
@@ -312,10 +312,8 @@ $$ LANGUAGE plpythonu;
PostgreSQL real, double,
and numeric are converted to
- Python float. Note that for
- the numeric this loses information and can lead to
- incorrect results. This might be fixed in a future
- release.
+ Python Decimal. This type is imported from
+ decimal.Decimal.
diff --git a/src/pl/plpython/expected/plpython_types.out b/src/pl/plpython/expected/plpython_types.out
index 4641345..2c4bb6a 100644
--- a/src/pl/plpython/expected/plpython_types.out
+++ b/src/pl/plpython/expected/plpython_types.out
@@ -216,31 +216,39 @@ CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$
plpy.info(x, type(x))
return x
$$ LANGUAGE plpythonu;
-/* The current implementation converts numeric to float. */
+/* The current implementation converts numeric to decimal.Decimal. */
SELECT * FROM test_type_conversion_numeric(100);
-INFO: (100.0, )
+INFO: (Decimal('100'), )
CONTEXT: PL/Python function "test_type_conversion_numeric"
test_type_conversion_numeric
------------------------------
- 100.0
+ 100
(1 row)
SELECT * FROM test_type_conversion_numeric(-100);
-INFO: (-100.0, )
+INFO: (Decimal('-100'), )
CONTEXT: PL/Python function "test_type_conversion_numeric"
test_type_conversion_numeric
------------------------------
- -100.0
+ -100
(1 row)
SELECT * FROM test_type_conversion_numeric(5000000000.5);
-INFO: (5000000000.5, )
+INFO: (Decimal('5000000000.5'), )
CONTEXT: PL/Python function "test_type_conversion_numeric"
test_type_conversion_numeric
------------------------------
5000000000.5
(1 row)
+SELECT * FROM test_type_conversion_numeric(1234567890.0987654321);
+INFO: (Decimal('1234567890.0987654321'), )
+CONTEXT: PL/Python function "test_type_conversion_numeric"
+ test_type_conversion_numeric
+------------------------------
+ 1234567890.0987654321
+(1 row)
+
SELECT * FROM test_type_conversion_numeric(null);
INFO: (None, )
CONTEXT: PL/Python function "test_type_conversion_numeric"
diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c
index 0dad843..ffce97d 100644
--- a/src/pl/plpython/plpy_main.c
+++ b/src/pl/plpython/plpy_main.c
@@ -77,6 +77,9 @@ static const int plpython_python_version = PY_MAJOR_VERSION;
/* initialize global variables */
PyObject *PLy_interp_globals = NULL;
+/* global pointer to decimal.Decimal costructor */
+PyObject *PLy_decimal_ctor_global = NULL;
+
/* this doesn't need to be global; use PLy_current_execution_context() */
static PLyExecutionContext *PLy_execution_contexts = NULL;
@@ -147,9 +150,23 @@ PLy_init_interp(void)
if (PLy_interp_safe_globals == NULL)
PLy_elog(ERROR, "could not create globals");
PyDict_SetItemString(PLy_interp_globals, "GD", PLy_interp_safe_globals);
- Py_DECREF(mainmod);
+
if (PLy_interp_globals == NULL || PyErr_Occurred())
PLy_elog(ERROR, "could not initialize globals");
+
+ PyObject *decimal = PyImport_ImportModule("decimal");
+ if (decimal == NULL)
+ PLy_elog(ERROR, "could not import module 'decimal'");
+
+ PyObject *decimal_dict = PyModule_GetDict(decimal);
+ if (decimal_dict == NULL)
+ PLy_elog(ERROR, "could not get decimal dict from imported module");
+
+ PLy_decimal_ctor_global = PyDict_GetItemString(decimal_dict, "Decimal");
+ if (PLy_decimal_ctor_global == NULL || !PyCallable_Check(PLy_decimal_ctor_global))
+ PLy_elog(ERROR, "could not get decimal consctructor for Decimal type");
+
+ Py_DECREF(mainmod);
}
Datum
diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c
index 8f2367d..83e0e50 100644
--- a/src/pl/plpython/plpy_typeio.c
+++ b/src/pl/plpython/plpy_typeio.c
@@ -18,6 +18,7 @@
#include "utils/memutils.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
+#include "utils/numeric.h"
#include "plpython.h"
@@ -26,6 +27,7 @@
#include "plpy_elog.h"
#include "plpy_main.h"
+extern PyObject *PLy_decimal_ctor_global;
/* I/O function caching */
static void PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup);
@@ -35,7 +37,7 @@ static void PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup);
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 *PLyDecimal_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);
@@ -450,7 +452,7 @@ PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup)
arg->func = PLyFloat_FromFloat8;
break;
case NUMERICOID:
- arg->func = PLyFloat_FromNumeric;
+ arg->func = PLyDecimal_FromNumeric;
break;
case INT2OID:
arg->func = PLyInt_FromInt16;
@@ -516,16 +518,12 @@ PLyFloat_FromFloat8(PLyDatumToOb *arg, Datum d)
}
static PyObject *
-PLyFloat_FromNumeric(PLyDatumToOb *arg, Datum d)
+PLyDecimal_FromNumeric(PLyDatumToOb *arg, Datum d)
{
- /*
- * 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);
+ char *x = DatumGetCString(DirectFunctionCall1(numeric_out, d));
+ PyObject *pvalue = PyString_FromString(x);
+ PyObject *value = PyObject_CallFunctionObjArgs(PLy_decimal_ctor_global, pvalue, NULL);
+ return value;
}
static PyObject *
diff --git a/src/pl/plpython/sql/plpython_types.sql b/src/pl/plpython/sql/plpython_types.sql
index 6a50b42..e35421f 100644
--- a/src/pl/plpython/sql/plpython_types.sql
+++ b/src/pl/plpython/sql/plpython_types.sql
@@ -90,10 +90,11 @@ plpy.info(x, type(x))
return x
$$ LANGUAGE plpythonu;
-/* The current implementation converts numeric to float. */
+/* The current implementation converts numeric to decimal.Decimal. */
SELECT * FROM test_type_conversion_numeric(100);
SELECT * FROM test_type_conversion_numeric(-100);
SELECT * FROM test_type_conversion_numeric(5000000000.5);
+SELECT * FROM test_type_conversion_numeric(1234567890.0987654321);
SELECT * FROM test_type_conversion_numeric(null);