Skip to content

Commit

Permalink
ENH: Implement comparison for string vs date and timestamp types
Browse files Browse the repository at this point in the history
Author: Phillip Cloud <cpcloud@gmail.com>

Closes #1065 from cpcloud/fix-temporal-comparison and squashes the following commits:

85e78c1 [Phillip Cloud] ENH: Implement comparison for string vs date and timestamp types
  • Loading branch information
cpcloud committed Jul 20, 2017
1 parent 7a1beee commit 19d6177
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 6 deletions.
2 changes: 1 addition & 1 deletion ibis/expr/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2014,7 +2014,7 @@ def _maybe_cast_args(self, left, right):
return left, left._implicit_cast(right)

if right._can_implicit_cast(left):
return right, right._implicit_cast(left)
return right._implicit_cast(left), right

return left, right

Expand Down
33 changes: 33 additions & 0 deletions ibis/expr/tests/test_value_exprs.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

import operator
from datetime import date, datetime

import pytest

Expand All @@ -23,6 +24,7 @@
import ibis.expr.operations as ops
import ibis

from ibis import literal
from ibis.tests.util import assert_equal


Expand Down Expand Up @@ -857,3 +859,34 @@ def test_generic_value_api_no_arithmetic(value, operation):
)
def test_fillna_null(value, expected):
assert ibis.NA.fillna(value).type().equals(expected)


@pytest.mark.parametrize(
('left', 'right'),
[
(literal('2017-04-01'), date(2017, 4, 2)),
(date(2017, 4, 2), literal('2017-04-01')),
(literal('2017-04-01 01:02:33'), datetime(2017, 4, 1, 1, 3, 34)),
(datetime(2017, 4, 1, 1, 3, 34), literal('2017-04-01 01:02:33')),
]
)
@pytest.mark.parametrize(
'op',
[
operator.eq,
operator.ne,
operator.lt,
operator.le,
operator.gt,
operator.ge,
lambda left, right: ibis.timestamp(
'2017-04-01 00:02:34'
).between(left, right),
lambda left, right: ibis.timestamp(
'2017-04-01'
).cast(dt.date).between(left, right)
]
)
def test_string_temporal_compare(op, left, right):
result = op(left, right)
assert result.type().equals(dt.boolean)
23 changes: 18 additions & 5 deletions ibis/expr/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import datetime
import webbrowser
import warnings
import sys

import six
import toolz
Expand Down Expand Up @@ -241,6 +242,12 @@ def _get_unbound_tables(self):
pass


if sys.version_info.major == 2:
# Python 2.7 doesn't return NotImplemented unless the other operand has
# an attribute called "timetuple". This is a bug that's fixed in Python 3
Expr.timetuple = None


def _safe_repr(x, memo=None):
return x._repr(memo=memo) if isinstance(x, (Expr, Node)) else repr(x)

Expand Down Expand Up @@ -956,7 +963,7 @@ def type(self):
return dt.string

def _can_compare(self, other):
return isinstance(other, StringValue)
return isinstance(other, (StringValue, TemporalValue))


class DecimalValue(NumericValue):
Expand All @@ -978,7 +985,11 @@ def constructor(arg, name=None):
return constructor


class DateValue(AnyValue):
class TemporalValue(AnyValue):
pass


class DateValue(TemporalValue):

def type(self):
return dt.date
Expand All @@ -995,15 +1006,15 @@ def _can_implicit_cast(self, arg):
return False

def _can_compare(self, other):
return isinstance(other, DateValue)
return isinstance(other, (TemporalValue, StringValue))

def _implicit_cast(self, arg):
# assume we've checked this is OK at this point...
op = arg.op()
return DateScalar(op)


class TimestampValue(AnyValue):
class TimestampValue(TemporalValue):

def __init__(self, meta=None):
self.meta = meta
Expand Down Expand Up @@ -1037,7 +1048,7 @@ def _can_implicit_cast(self, arg):
return False

def _can_compare(self, other):
return isinstance(other, TimestampValue)
return isinstance(other, (TemporalValue, StringValue))

def _implicit_cast(self, arg):
# assume we've checked this is OK at this point...
Expand Down Expand Up @@ -1320,6 +1331,8 @@ def literal(value, type=None):
...
TypeError: Value 'foobar' cannot be safely coerced to int64
"""
if hasattr(value, 'op') and isinstance(value.op(), Literal):
return value

if type is None:
type = infer_literal_type(value)
Expand Down
74 changes: 74 additions & 0 deletions ibis/sql/postgres/tests/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import operator
import unittest

from datetime import date, datetime

import pytest
import string

Expand Down Expand Up @@ -1160,3 +1162,75 @@ def test_timestamp_type_accepts_all_timezones():
]
for zone in zones:
assert dt.Timestamp(zone).timezone == zone


@pytest.mark.postgresql
@pytest.mark.parametrize(
('left', 'right', 'type'),
[
(L('2017-04-01'), date(2017, 4, 2), dt.date),
(date(2017, 4, 2), L('2017-04-01'), dt.date),
(
L('2017-04-01 01:02:33'),
datetime(2017, 4, 1, 1, 3, 34),
dt.timestamp
),
(
datetime(2017, 4, 1, 1, 3, 34),
L('2017-04-01 01:02:33'),
dt.timestamp
),
]
)
@pytest.mark.parametrize(
'op',
[
operator.eq,
operator.ne,
operator.lt,
operator.le,
operator.gt,
operator.ge,
]
)
def test_string_temporal_compare(con, op, left, right, type):
expr = op(left, right)
result = con.execute(expr)
left_raw = con.execute(L(left).cast(type))
right_raw = con.execute(L(right).cast(type))
expected = op(left_raw, right_raw)
assert result == expected


@pytest.mark.postgresql
@pytest.mark.parametrize(
('left', 'right'),
[
(L('2017-03-31').cast(dt.date), date(2017, 4, 2)),
(date(2017, 3, 31), L('2017-04-02').cast(dt.date)),
(
L('2017-03-31 00:02:33').cast(dt.timestamp),
datetime(2017, 4, 1, 1, 3, 34),
),
(
datetime(2017, 3, 31, 0, 2, 33),
L('2017-04-01 01:03:34').cast(dt.timestamp),
),
]
)
@pytest.mark.parametrize(
'op',
[
lambda left, right: ibis.timestamp('2017-04-01 00:02:34').between(
left, right
),
lambda left, right: ibis.timestamp('2017-04-01').cast(dt.date).between(
left, right
),
]
)
def test_string_temporal_compare_between(con, op, left, right):
expr = op(left, right)
result = con.execute(expr)
assert isinstance(result, (bool, np.bool_))
assert result

0 comments on commit 19d6177

Please sign in to comment.