diff --git a/ChangeLog b/ChangeLog index 582dca76..34b4c1ac 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,12 @@ -2009-01-20 Federico Di Gregorio + + * Fixed problem mailed by Markus Demleitner about Python to + PostgreSQL conversions of "nan" and "inf" floats resulting in + backend errors. Added the new psycopg2.extensions.Float adapter + that correctly handle both values and unit tests. (The opposite + conversion was already working.) + +2009-01-20 Federico Di Gregorio * Fixed problem reported by Lawrence Oluyede where register_type() didn't work on connection and cursors diff --git a/lib/extensions.py b/lib/extensions.py index 4966eae3..aaaa87e5 100644 --- a/lib/extensions.py +++ b/lib/extensions.py @@ -31,7 +31,7 @@ from _psycopg import DECIMALARRAY, FLOATARRAY, INTEGERARRAY, INTERVALARRAY from _psycopg import LONGINTEGERARRAY, ROWIDARRAY, STRINGARRAY, TIMEARRAY from _psycopg import UNICODEARRAY -from _psycopg import Boolean, QuotedString, AsIs +from _psycopg import Boolean, Float, QuotedString, AsIs try: from _psycopg import DateFromMx, TimeFromMx, TimestampFromMx from _psycopg import IntervalFromMx diff --git a/psycopg/adapter_pfloat.c b/psycopg/adapter_pfloat.c new file mode 100644 index 00000000..10c05bd8 --- /dev/null +++ b/psycopg/adapter_pfloat.c @@ -0,0 +1,246 @@ +/* adapter_float.c - psycopg pfloat type wrapper implementation + * + * Copyright (C) 2003-2009 Federico Di Gregorio + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#define PY_SSIZE_T_CLEAN +#include +#include +#include +#include + +#define PSYCOPG_MODULE +#include "psycopg/config.h" +#include "psycopg/python.h" +#include "psycopg/psycopg.h" +#include "psycopg/adapter_pfloat.h" +#include "psycopg/microprotocols_proto.h" + + +/** the Float object **/ + +static PyObject * +pfloat_str(pfloatObject *self) +{ + double n = PyFloat_AsDouble(self->wrapped); + if (isnan(n)) + return PyString_FromString("'NaN'::float"); + else if (isinf(n)) + return PyString_FromString("'Infinity'::float"); + else + return PyObject_Str(self->wrapped); +} + +static PyObject * +pfloat_getquoted(pfloatObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) return NULL; + return pfloat_str(self); +} + +static PyObject * +pfloat_conform(pfloatObject *self, PyObject *args) +{ + PyObject *res, *proto; + + if (!PyArg_ParseTuple(args, "O", &proto)) return NULL; + + if (proto == (PyObject*)&isqlquoteType) + res = (PyObject*)self; + else + res = Py_None; + + Py_INCREF(res); + return res; +} + +/** the Float object */ + +/* object member list */ + +static struct PyMemberDef pfloatObject_members[] = { + {"adapted", T_OBJECT, offsetof(pfloatObject, wrapped), RO}, + {NULL} +}; + +/* object method table */ + +static PyMethodDef pfloatObject_methods[] = { + {"getquoted", (PyCFunction)pfloat_getquoted, METH_VARARGS, + "getquoted() -> wrapped object value as SQL-quoted string"}, + {"__conform__", (PyCFunction)pfloat_conform, METH_VARARGS, NULL}, + {NULL} /* Sentinel */ +}; + +/* initialization and finalization methods */ + +static int +pfloat_setup(pfloatObject *self, PyObject *obj) +{ + Dprintf("pfloat_setup: init pfloat object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, ((PyObject *)self)->ob_refcnt + ); + + Py_INCREF(obj); + self->wrapped = obj; + + Dprintf("pfloat_setup: good pfloat object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, ((PyObject *)self)->ob_refcnt + ); + return 0; +} + +static int +pfloat_traverse(PyObject *obj, visitproc visit, void *arg) +{ + pfloatObject *self = (pfloatObject *)obj; + + Py_VISIT(self->wrapped); + return 0; +} + +static void +pfloat_dealloc(PyObject* obj) +{ + pfloatObject *self = (pfloatObject *)obj; + + Py_CLEAR(self->wrapped); + + Dprintf("pfloat_dealloc: deleted pfloat object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + obj, obj->ob_refcnt + ); + + obj->ob_type->tp_free(obj); +} + +static int +pfloat_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *o; + + if (!PyArg_ParseTuple(args, "O", &o)) + return -1; + + return pfloat_setup((pfloatObject *)obj, o); +} + +static PyObject * +pfloat_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + +static void +pfloat_del(PyObject* self) +{ + PyObject_GC_Del(self); +} + +static PyObject * +pfloat_repr(pfloatObject *self) +{ + return PyString_FromFormat("", + self); +} + + +/* object type */ + +#define pfloatType_doc \ +"Float(str) -> new Float adapter object" + +PyTypeObject pfloatType = { + PyObject_HEAD_INIT(NULL) + 0, + "psycopg2._psycopg.Float", + sizeof(pfloatObject), + 0, + pfloat_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + + 0, /*tp_compare*/ + + (reprfunc)pfloat_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + + 0, /*tp_call*/ + (reprfunc)pfloat_str, /*tp_str*/ + + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/ + pfloatType_doc, /*tp_doc*/ + + pfloat_traverse, /*tp_traverse*/ + 0, /*tp_clear*/ + + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + + /* Attribute descriptor and subclassing stuff */ + + pfloatObject_methods, /*tp_methods*/ + pfloatObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + + pfloat_init, /*tp_init*/ + 0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/ + pfloat_new, /*tp_new*/ + (freefunc)pfloat_del, /*tp_free Low-level free-memory routine */ + 0, /*tp_is_gc For PyObject_IS_GC */ + 0, /*tp_bases*/ + 0, /*tp_mro method resolution order */ + 0, /*tp_cache*/ + 0, /*tp_subclasses*/ + 0 /*tp_weaklist*/ +}; + + +/** module-level functions **/ + +PyObject * +psyco_Float(PyObject *module, PyObject *args) +{ + PyObject *obj; + + if (!PyArg_ParseTuple(args, "O", &obj)) + return NULL; + + return PyObject_CallFunction((PyObject *)&pfloatType, "O", obj); +} diff --git a/psycopg/adapter_pfloat.h b/psycopg/adapter_pfloat.h new file mode 100644 index 00000000..07ea7a2a --- /dev/null +++ b/psycopg/adapter_pfloat.h @@ -0,0 +1,54 @@ +/* adapter_pfloat.h - definition for the psycopg float type wrapper + * + * Copyright (C) 2003-2004 Federico Di Gregorio + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PSYCOPG_PFLOAT_H +#define PSYCOPG_PFLOAT_H 1 + +#define PY_SSIZE_T_CLEAN +#include + +#include "psycopg/config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern HIDDEN PyTypeObject pfloatType; + +typedef struct { + PyObject_HEAD + + /* this is the real object we wrap */ + PyObject *wrapped; + +} pfloatObject; + +/* functions exported to psycopgmodule.c */ + +HIDDEN PyObject *psyco_Float(PyObject *module, PyObject *args); +#define psyco_Float_doc \ + "Float(obj) -> new float value" + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_PFLOAT_H) */ diff --git a/psycopg/psycopgmodule.c b/psycopg/psycopgmodule.c index eb05b84c..4cef9934 100644 --- a/psycopg/psycopgmodule.c +++ b/psycopg/psycopgmodule.c @@ -36,6 +36,7 @@ #include "psycopg/adapter_qstring.h" #include "psycopg/adapter_binary.h" #include "psycopg/adapter_pboolean.h" +#include "psycopg/adapter_pfloat.h" #include "psycopg/adapter_asis.h" #include "psycopg/adapter_list.h" #include "psycopg/typecast_binary.h" @@ -271,7 +272,7 @@ psyco_adapters_init(PyObject *mod) { PyObject *call; - microprotocols_add(&PyFloat_Type, NULL, (PyObject*)&asisType); + microprotocols_add(&PyFloat_Type, NULL, (PyObject*)&pfloatType); microprotocols_add(&PyInt_Type, NULL, (PyObject*)&asisType); microprotocols_add(&PyLong_Type, NULL, (PyObject*)&asisType); microprotocols_add(&PyBool_Type, NULL, (PyObject*)&pbooleanType); @@ -629,6 +630,8 @@ static PyMethodDef psycopgMethods[] = { {"QuotedString", (PyCFunction)psyco_QuotedString, METH_VARARGS, psyco_QuotedString_doc}, {"Boolean", (PyCFunction)psyco_Boolean, + METH_VARARGS, psyco_Float_doc}, + {"Float", (PyCFunction)psyco_Float, METH_VARARGS, psyco_Boolean_doc}, {"Binary", (PyCFunction)psyco_Binary, METH_VARARGS, psyco_Binary_doc}, @@ -693,6 +696,7 @@ init_psycopg(void) binaryType.ob_type = &PyType_Type; isqlquoteType.ob_type = &PyType_Type; pbooleanType.ob_type = &PyType_Type; + pfloatType.ob_type = &PyType_Type; asisType.ob_type = &PyType_Type; listType.ob_type = &PyType_Type; chunkType.ob_type = &PyType_Type; @@ -704,6 +708,7 @@ init_psycopg(void) if (PyType_Ready(&binaryType) == -1) return; if (PyType_Ready(&isqlquoteType) == -1) return; if (PyType_Ready(&pbooleanType) == -1) return; + if (PyType_Ready(&pfloatType) == -1) return; if (PyType_Ready(&asisType) == -1) return; if (PyType_Ready(&listType) == -1) return; if (PyType_Ready(&chunkType) == -1) return; @@ -804,6 +809,7 @@ init_psycopg(void) binaryType.tp_alloc = PyType_GenericAlloc; isqlquoteType.tp_alloc = PyType_GenericAlloc; pbooleanType.tp_alloc = PyType_GenericAlloc; + pfloatType.tp_alloc = PyType_GenericAlloc; connectionType.tp_alloc = PyType_GenericAlloc; asisType.tp_alloc = PyType_GenericAlloc; qstringType.tp_alloc = PyType_GenericAlloc; diff --git a/psycopg2.mdp b/psycopg2.mdp index f3dff0fd..6745490d 100644 --- a/psycopg2.mdp +++ b/psycopg2.mdp @@ -142,6 +142,8 @@ + + diff --git a/psycopg2.mds b/psycopg2.mds index eedc1317..bb20e604 100644 --- a/psycopg2.mds +++ b/psycopg2.mds @@ -11,6 +11,6 @@ - + \ No newline at end of file diff --git a/setup.py b/setup.py index 10a04450..02daa410 100644 --- a/setup.py +++ b/setup.py @@ -316,7 +316,8 @@ sources = [ 'connection_type.c', 'connection_int.c', 'cursor_type.c', 'cursor_int.c', 'lobject_type.c', 'lobject_int.c', 'adapter_qstring.c', 'adapter_pboolean.c', 'adapter_binary.c', - 'adapter_asis.c', 'adapter_list.c', 'adapter_datetime.c', 'utils.c'] + 'adapter_asis.c', 'adapter_list.c', 'adapter_datetime.c', 'adapter_pfloat.c', + 'utils.c'] parser = ConfigParser.ConfigParser() parser.read('setup.cfg') diff --git a/tests/types_basic.py b/tests/types_basic.py index 323f5fbf..180bb2ae 100755 --- a/tests/types_basic.py +++ b/tests/types_basic.py @@ -60,6 +60,14 @@ class TypesBasicTests(unittest.TestCase): self.failUnless(abs(s - 19.10) < 0.001, "wrong float quoting: " + str(s)) + def testFloat(self): + s = self.execute("SELECT %s AS foo", (float("nan"),)) + self.failUnless(str(s) == "nan", "wrong float quoting: " + str(s)) + self.failUnless(type(s) == float, "wrong float conversion: " + repr(s)) + s = self.execute("SELECT %s AS foo", (float("inf"),)) + self.failUnless(str(s) == "inf", "wrong float quoting: " + str(s)) + self.failUnless(type(s) == float, "wrong float conversion: " + repr(s)) + def testBinary(self): s = ''.join([chr(x) for x in range(256)]) b = psycopg2.Binary(s)