Merge branch 'fix-558'

This commit is contained in:
Daniele Varrazzo 2017-06-16 19:41:52 +01:00
commit 2b5e131831
3 changed files with 75 additions and 0 deletions

1
NEWS
View File

@ -21,6 +21,7 @@ What's new in psycopg 2.7.2
- Fixed `~psycopg2.extras.ReplicationCursor.consume_stream()` - Fixed `~psycopg2.extras.ReplicationCursor.consume_stream()`
*keepalive_interval* argument (:ticket:`#547`). *keepalive_interval* argument (:ticket:`#547`).
- Fixed random `!SystemError` upon receiving abort signal (:ticket:`#551`). - 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 - Added `~psycopg2.extras.Json` `!prepare()` method to consider connection
params when adapting (:ticket:`#562`). params when adapting (:ticket:`#562`).
- `~psycopg2.errorcodes` map updated to PostgreSQL 10 beta 1. - `~psycopg2.errorcodes` map updated to PostgreSQL 10 beta 1.

View File

@ -276,6 +276,44 @@ typecast_PYTIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
return obj; 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 **/ /** INTERVAL - parse an interval into a timedelta object **/
static PyObject * 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; long v = 0, years = 0, months = 0, hours = 0, minutes = 0, micros = 0;
PY_LONG_LONG days = 0, seconds = 0; PY_LONG_LONG days = 0, seconds = 0;
int sign = 1, denom = 1, part = 0; int sign = 1, denom = 1, part = 0;
const char *orig = str;
if (str == NULL) { Py_RETURN_NONE; } 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 * or too big value. On Win where long == int the 2nd check
* is useless. */ * is useless. */
if (v1 < v || v1 > (long)INT_MAX) { 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( PyErr_SetString(
PyExc_OverflowError, "interval component too big"); PyExc_OverflowError, "interval component too big");
return NULL; 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); 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 */ /* add hour, minutes, seconds together and include the sign */
seconds += 60 * (PY_LONG_LONG)minutes + 3600 * (PY_LONG_LONG)hours; seconds += 60 * (PY_LONG_LONG)minutes + 3600 * (PY_LONG_LONG)hours;

View File

@ -411,6 +411,27 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
self.assert_(t.tzinfo is not None) self.assert_(t.tzinfo is not None)
self.assert_(t < datetime(1000, 1, 1, tzinfo=FixedOffsetTimezone())) 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. # Only run the datetime tests if psycopg was compiled with support.
if not hasattr(psycopg2.extensions, 'PYDATETIME'): if not hasattr(psycopg2.extensions, 'PYDATETIME'):