diff options
author | Daniele Varrazzo <daniele.varrazzo@gmail.com> | 2017-06-16 19:41:52 +0100 |
---|---|---|
committer | Daniele Varrazzo <daniele.varrazzo@gmail.com> | 2017-06-16 19:41:52 +0100 |
commit | 2b5e1318316a064bbc598ab95d9b214fbda3c99e (patch) | |
tree | 9025e5bc8622485aaad2aecfbc955c381cc43eb1 | |
parent | 324e56cfa354a737665068ffdecfb1fc17bb8c12 (diff) | |
parent | 70a2d2238e84ea8382bf994f2f79a008dbf5984d (diff) | |
download | psycopg2-2b5e1318316a064bbc598ab95d9b214fbda3c99e.tar.gz |
Merge branch 'fix-558'
-rw-r--r-- | NEWS | 1 | ||||
-rw-r--r-- | psycopg/typecast_datetime.c | 53 | ||||
-rwxr-xr-x | tests/test_dates.py | 21 |
3 files changed, 75 insertions, 0 deletions
@@ -21,6 +21,7 @@ What's new in psycopg 2.7.2 - Fixed `~psycopg2.extras.ReplicationCursor.consume_stream()` *keepalive_interval* argument (:ticket:`#547`). - Fixed random `!SystemError` upon receiving abort signal (:ticket:`#551`). +- Parse intervals returned as microseconds from Redshift (:ticket:`#558`). - Added `~psycopg2.extras.Json` `!prepare()` method to consider connection params when adapting (:ticket:`#562`). - `~psycopg2.errorcodes` map updated to PostgreSQL 10 beta 1. diff --git a/psycopg/typecast_datetime.c b/psycopg/typecast_datetime.c index fec57de..b0b257b 100644 --- a/psycopg/typecast_datetime.c +++ b/psycopg/typecast_datetime.c @@ -276,6 +276,44 @@ typecast_PYTIME_cast(const char *str, Py_ssize_t len, PyObject *curs) return obj; } + +/* Attempt parsing a number as microseconds + * Redshift is reported returning this stuff, see #558 + * + * Return a new `timedelta()` object in case of success or NULL and set an error + */ +static PyObject * +interval_from_usecs(const char *str) +{ + PyObject *us = NULL; + char *pend; + PyObject *rv = NULL; + + Dprintf("interval_from_usecs: %s", str); + + if (!(us = PyLong_FromString((char *)str, &pend, 0))) { + Dprintf("interval_from_usecs: parsing long failed"); + goto exit; + } + + if (*pend != '\0') { + /* there are trailing chars, it's not just micros. Barf. */ + Dprintf("interval_from_usecs: spurious chars %s", pend); + PyErr_Format(PyExc_ValueError, + "expected number of microseconds, got %s", str); + goto exit; + } + + rv = PyObject_CallFunction( + (PyObject*)PyDateTimeAPI->DeltaType, "LLO", + 0L, 0L, us); + +exit: + Py_XDECREF(us); + return rv; +} + + /** INTERVAL - parse an interval into a timedelta object **/ static PyObject * @@ -284,6 +322,7 @@ typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs) long v = 0, years = 0, months = 0, hours = 0, minutes = 0, micros = 0; PY_LONG_LONG days = 0, seconds = 0; int sign = 1, denom = 1, part = 0; + const char *orig = str; if (str == NULL) { Py_RETURN_NONE; } @@ -305,6 +344,16 @@ typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs) * or too big value. On Win where long == int the 2nd check * is useless. */ if (v1 < v || v1 > (long)INT_MAX) { + /* uhm, oops... but before giving up, maybe it's redshift + * returning microseconds? See #558 */ + PyObject *rv; + if ((rv = interval_from_usecs(orig))) { + return rv; + } + else { + PyErr_Clear(); + } + PyErr_SetString( PyExc_OverflowError, "interval component too big"); return NULL; @@ -384,6 +433,10 @@ typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs) micros = (long)round((double)micros / denom * 1000000.0); } } + else if (part == 0) { + /* Parsing failed, maybe it's just an integer? Assume usecs */ + return interval_from_usecs(orig); + } /* add hour, minutes, seconds together and include the sign */ seconds += 60 * (PY_LONG_LONG)minutes + 3600 * (PY_LONG_LONG)hours; diff --git a/tests/test_dates.py b/tests/test_dates.py index 83eea32..0b790d0 100755 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -411,6 +411,27 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin): self.assert_(t.tzinfo is not None) self.assert_(t < datetime(1000, 1, 1, tzinfo=FixedOffsetTimezone())) + def test_redshift_day(self): + # Redshift is reported returning 1 day interval as microsec (bug #558) + cur = self.conn.cursor() + psycopg2.extensions.register_type( + psycopg2.extensions.new_type( + psycopg2.STRING.values, 'WAT', psycopg2.extensions.INTERVAL), + cur) + + from datetime import timedelta + for s, v in [ + ('0', timedelta(0)), + ('1', timedelta(microseconds=1)), + ('-1', timedelta(microseconds=-1)), + ('1000000', timedelta(seconds=1)), + ('86400000000', timedelta(days=1)), + ('-86400000000', timedelta(days=-1)), + ]: + cur.execute("select %s::text", (s,)) + r = cur.fetchone()[0] + self.assertEqual(r, v, "%s -> %s != %s" % (s, r, v)) + # Only run the datetime tests if psycopg was compiled with support. if not hasattr(psycopg2.extensions, 'PYDATETIME'): |