mirror of
https://github.com/postgres/postgres.git
synced 2025-06-04 00:02:37 -04:00
PL/Python: Convert numeric to Decimal
The old implementation converted PostgreSQL numeric to Python float, which was always considered a shortcoming. Now numeric is converted to the Python Decimal object. Either the external cdecimal module or the standard library decimal module are supported. From: Szymon Guz <mabewlun@gmail.com> From: Ronan Dunklau <rdunklau@gmail.com> Reviewed-by: Steve Singer <steve@ssinger.info>
This commit is contained in:
parent
02d2b694ee
commit
7919398bac
@ -310,12 +310,23 @@ $$ LANGUAGE plpythonu;
|
|||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
PostgreSQL <type>real</type>, <type>double</type>,
|
PostgreSQL <type>real</type> and <type>double</type> are converted to
|
||||||
and <type>numeric</type> are converted to
|
Python <type>float</type>.
|
||||||
Python <type>float</type>. Note that for
|
</para>
|
||||||
the <type>numeric</type> this loses information and can lead to
|
</listitem>
|
||||||
incorrect results. This might be fixed in a future
|
|
||||||
release.
|
<listitem>
|
||||||
|
<para>
|
||||||
|
PostgreSQL <type>numeric</type> is converted to
|
||||||
|
Python <type>Decimal</type>. This type is imported from
|
||||||
|
the <literal>cdecimal</literal> package if that is available.
|
||||||
|
Otherwise,
|
||||||
|
<literal>decimal.Decimal</literal> from the standard library will be
|
||||||
|
used. <literal>cdecimal</literal> is significantly faster
|
||||||
|
than <literal>decimal</literal>. In Python 3.3,
|
||||||
|
however, <literal>cdecimal</literal> has been integrated into the
|
||||||
|
standard library under the name <literal>decimal</literal>, so there is
|
||||||
|
no longer any difference.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
|
@ -213,36 +213,69 @@ CONTEXT: PL/Python function "test_type_conversion_int8"
|
|||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$
|
CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$
|
||||||
plpy.info(x, type(x))
|
# print just the class name, not the type, to avoid differences
|
||||||
|
# between decimal and cdecimal
|
||||||
|
plpy.info(x, x.__class__.__name__)
|
||||||
return x
|
return x
|
||||||
$$ LANGUAGE plpythonu;
|
$$ LANGUAGE plpythonu;
|
||||||
/* The current implementation converts numeric to float. */
|
|
||||||
SELECT * FROM test_type_conversion_numeric(100);
|
SELECT * FROM test_type_conversion_numeric(100);
|
||||||
INFO: (100.0, <type 'float'>)
|
INFO: (Decimal('100'), 'Decimal')
|
||||||
|
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
||||||
|
test_type_conversion_numeric
|
||||||
|
------------------------------
|
||||||
|
100
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_conversion_numeric(-100);
|
||||||
|
INFO: (Decimal('-100'), 'Decimal')
|
||||||
|
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
||||||
|
test_type_conversion_numeric
|
||||||
|
------------------------------
|
||||||
|
-100
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_conversion_numeric(100.0);
|
||||||
|
INFO: (Decimal('100.0'), 'Decimal')
|
||||||
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
||||||
test_type_conversion_numeric
|
test_type_conversion_numeric
|
||||||
------------------------------
|
------------------------------
|
||||||
100.0
|
100.0
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT * FROM test_type_conversion_numeric(-100);
|
SELECT * FROM test_type_conversion_numeric(100.00);
|
||||||
INFO: (-100.0, <type 'float'>)
|
INFO: (Decimal('100.00'), 'Decimal')
|
||||||
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
||||||
test_type_conversion_numeric
|
test_type_conversion_numeric
|
||||||
------------------------------
|
------------------------------
|
||||||
-100.0
|
100.00
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT * FROM test_type_conversion_numeric(5000000000.5);
|
SELECT * FROM test_type_conversion_numeric(5000000000.5);
|
||||||
INFO: (5000000000.5, <type 'float'>)
|
INFO: (Decimal('5000000000.5'), 'Decimal')
|
||||||
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
||||||
test_type_conversion_numeric
|
test_type_conversion_numeric
|
||||||
------------------------------
|
------------------------------
|
||||||
5000000000.5
|
5000000000.5
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_conversion_numeric(1234567890.0987654321);
|
||||||
|
INFO: (Decimal('1234567890.0987654321'), 'Decimal')
|
||||||
|
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
||||||
|
test_type_conversion_numeric
|
||||||
|
------------------------------
|
||||||
|
1234567890.0987654321
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_conversion_numeric(-1234567890.0987654321);
|
||||||
|
INFO: (Decimal('-1234567890.0987654321'), 'Decimal')
|
||||||
|
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
||||||
|
test_type_conversion_numeric
|
||||||
|
------------------------------
|
||||||
|
-1234567890.0987654321
|
||||||
|
(1 row)
|
||||||
|
|
||||||
SELECT * FROM test_type_conversion_numeric(null);
|
SELECT * FROM test_type_conversion_numeric(null);
|
||||||
INFO: (None, <type 'NoneType'>)
|
INFO: (None, 'NoneType')
|
||||||
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
||||||
test_type_conversion_numeric
|
test_type_conversion_numeric
|
||||||
------------------------------
|
------------------------------
|
||||||
|
@ -213,36 +213,69 @@ CONTEXT: PL/Python function "test_type_conversion_int8"
|
|||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$
|
CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$
|
||||||
plpy.info(x, type(x))
|
# print just the class name, not the type, to avoid differences
|
||||||
|
# between decimal and cdecimal
|
||||||
|
plpy.info(x, x.__class__.__name__)
|
||||||
return x
|
return x
|
||||||
$$ LANGUAGE plpython3u;
|
$$ LANGUAGE plpython3u;
|
||||||
/* The current implementation converts numeric to float. */
|
|
||||||
SELECT * FROM test_type_conversion_numeric(100);
|
SELECT * FROM test_type_conversion_numeric(100);
|
||||||
INFO: (100.0, <class 'float'>)
|
INFO: (Decimal('100'), 'Decimal')
|
||||||
|
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
||||||
|
test_type_conversion_numeric
|
||||||
|
------------------------------
|
||||||
|
100
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_conversion_numeric(-100);
|
||||||
|
INFO: (Decimal('-100'), 'Decimal')
|
||||||
|
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
||||||
|
test_type_conversion_numeric
|
||||||
|
------------------------------
|
||||||
|
-100
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_conversion_numeric(100.0);
|
||||||
|
INFO: (Decimal('100.0'), 'Decimal')
|
||||||
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
||||||
test_type_conversion_numeric
|
test_type_conversion_numeric
|
||||||
------------------------------
|
------------------------------
|
||||||
100.0
|
100.0
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT * FROM test_type_conversion_numeric(-100);
|
SELECT * FROM test_type_conversion_numeric(100.00);
|
||||||
INFO: (-100.0, <class 'float'>)
|
INFO: (Decimal('100.00'), 'Decimal')
|
||||||
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
||||||
test_type_conversion_numeric
|
test_type_conversion_numeric
|
||||||
------------------------------
|
------------------------------
|
||||||
-100.0
|
100.00
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT * FROM test_type_conversion_numeric(5000000000.5);
|
SELECT * FROM test_type_conversion_numeric(5000000000.5);
|
||||||
INFO: (5000000000.5, <class 'float'>)
|
INFO: (Decimal('5000000000.5'), 'Decimal')
|
||||||
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
||||||
test_type_conversion_numeric
|
test_type_conversion_numeric
|
||||||
------------------------------
|
------------------------------
|
||||||
5000000000.5
|
5000000000.5
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_conversion_numeric(1234567890.0987654321);
|
||||||
|
INFO: (Decimal('1234567890.0987654321'), 'Decimal')
|
||||||
|
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
||||||
|
test_type_conversion_numeric
|
||||||
|
------------------------------
|
||||||
|
1234567890.0987654321
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_conversion_numeric(-1234567890.0987654321);
|
||||||
|
INFO: (Decimal('-1234567890.0987654321'), 'Decimal')
|
||||||
|
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
||||||
|
test_type_conversion_numeric
|
||||||
|
------------------------------
|
||||||
|
-1234567890.0987654321
|
||||||
|
(1 row)
|
||||||
|
|
||||||
SELECT * FROM test_type_conversion_numeric(null);
|
SELECT * FROM test_type_conversion_numeric(null);
|
||||||
INFO: (None, <class 'NoneType'>)
|
INFO: (None, 'NoneType')
|
||||||
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
CONTEXT: PL/Python function "test_type_conversion_numeric"
|
||||||
test_type_conversion_numeric
|
test_type_conversion_numeric
|
||||||
------------------------------
|
------------------------------
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
|
#include "utils/numeric.h"
|
||||||
#include "utils/syscache.h"
|
#include "utils/syscache.h"
|
||||||
#include "utils/typcache.h"
|
#include "utils/typcache.h"
|
||||||
|
|
||||||
@ -35,7 +36,7 @@ static void PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup);
|
|||||||
static PyObject *PLyBool_FromBool(PLyDatumToOb *arg, Datum d);
|
static PyObject *PLyBool_FromBool(PLyDatumToOb *arg, Datum d);
|
||||||
static PyObject *PLyFloat_FromFloat4(PLyDatumToOb *arg, Datum d);
|
static PyObject *PLyFloat_FromFloat4(PLyDatumToOb *arg, Datum d);
|
||||||
static PyObject *PLyFloat_FromFloat8(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_FromInt16(PLyDatumToOb *arg, Datum d);
|
||||||
static PyObject *PLyInt_FromInt32(PLyDatumToOb *arg, Datum d);
|
static PyObject *PLyInt_FromInt32(PLyDatumToOb *arg, Datum d);
|
||||||
static PyObject *PLyLong_FromInt64(PLyDatumToOb *arg, Datum d);
|
static PyObject *PLyLong_FromInt64(PLyDatumToOb *arg, Datum d);
|
||||||
@ -450,7 +451,7 @@ PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup)
|
|||||||
arg->func = PLyFloat_FromFloat8;
|
arg->func = PLyFloat_FromFloat8;
|
||||||
break;
|
break;
|
||||||
case NUMERICOID:
|
case NUMERICOID:
|
||||||
arg->func = PLyFloat_FromNumeric;
|
arg->func = PLyDecimal_FromNumeric;
|
||||||
break;
|
break;
|
||||||
case INT2OID:
|
case INT2OID:
|
||||||
arg->func = PLyInt_FromInt16;
|
arg->func = PLyInt_FromInt16;
|
||||||
@ -516,16 +517,37 @@ PLyFloat_FromFloat8(PLyDatumToOb *arg, Datum d)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
PLyFloat_FromNumeric(PLyDatumToOb *arg, Datum d)
|
PLyDecimal_FromNumeric(PLyDatumToOb *arg, Datum d)
|
||||||
{
|
{
|
||||||
/*
|
static PyObject *decimal_constructor;
|
||||||
* Numeric is cast to a PyFloat: This results in a loss of precision Would
|
char *str;
|
||||||
* it be better to cast to PyString?
|
PyObject *pyvalue;
|
||||||
*/
|
|
||||||
Datum f = DirectFunctionCall1(numeric_float8, d);
|
|
||||||
double x = DatumGetFloat8(f);
|
|
||||||
|
|
||||||
return PyFloat_FromDouble(x);
|
/* Try to import cdecimal. If it doesn't exist, fall back to decimal. */
|
||||||
|
if (!decimal_constructor)
|
||||||
|
{
|
||||||
|
PyObject *decimal_module;
|
||||||
|
|
||||||
|
decimal_module = PyImport_ImportModule("cdecimal");
|
||||||
|
if (!decimal_module)
|
||||||
|
{
|
||||||
|
PyErr_Clear();
|
||||||
|
decimal_module = PyImport_ImportModule("decimal");
|
||||||
|
}
|
||||||
|
if (!decimal_module)
|
||||||
|
PLy_elog(ERROR, "could not import a module for Decimal constructor");
|
||||||
|
|
||||||
|
decimal_constructor = PyObject_GetAttrString(decimal_module, "Decimal");
|
||||||
|
if (!decimal_constructor)
|
||||||
|
PLy_elog(ERROR, "no Decimal attribute in module");
|
||||||
|
}
|
||||||
|
|
||||||
|
str = DatumGetCString(DirectFunctionCall1(numeric_out, d));
|
||||||
|
pyvalue = PyObject_CallFunction(decimal_constructor, "s", str);
|
||||||
|
if (!pyvalue)
|
||||||
|
PLy_elog(ERROR, "conversion from numeric to Decimal failed");
|
||||||
|
|
||||||
|
return pyvalue;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -86,14 +86,19 @@ SELECT * FROM test_type_conversion_int8(null);
|
|||||||
|
|
||||||
|
|
||||||
CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$
|
CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$
|
||||||
plpy.info(x, type(x))
|
# print just the class name, not the type, to avoid differences
|
||||||
|
# between decimal and cdecimal
|
||||||
|
plpy.info(x, x.__class__.__name__)
|
||||||
return x
|
return x
|
||||||
$$ LANGUAGE plpythonu;
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
/* The current implementation converts numeric to float. */
|
|
||||||
SELECT * FROM test_type_conversion_numeric(100);
|
SELECT * FROM test_type_conversion_numeric(100);
|
||||||
SELECT * FROM test_type_conversion_numeric(-100);
|
SELECT * FROM test_type_conversion_numeric(-100);
|
||||||
|
SELECT * FROM test_type_conversion_numeric(100.0);
|
||||||
|
SELECT * FROM test_type_conversion_numeric(100.00);
|
||||||
SELECT * FROM test_type_conversion_numeric(5000000000.5);
|
SELECT * FROM test_type_conversion_numeric(5000000000.5);
|
||||||
|
SELECT * FROM test_type_conversion_numeric(1234567890.0987654321);
|
||||||
|
SELECT * FROM test_type_conversion_numeric(-1234567890.0987654321);
|
||||||
SELECT * FROM test_type_conversion_numeric(null);
|
SELECT * FROM test_type_conversion_numeric(null);
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user