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);