Skip to content

Commit

Permalink
BUG: PostgreSQL interval type not recognized
Browse files Browse the repository at this point in the history
Closes #1661
Author: Phillip Cloud <cpcloud@gmail.com>

Closes #1754 from cpcloud/gh-1661 and squashes the following commits:

a16d51a [Phillip Cloud] BUG: PostgreSQL interval type not recognized
  • Loading branch information
cpcloud committed Apr 4, 2019
1 parent b00caa2 commit 65bdb04
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 4 deletions.
12 changes: 9 additions & 3 deletions ci/datamgr.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,14 @@ def mapd(schema, tables, data_directory, **params):
port=params['port'],
dbname='mapd',
)
stmt = 'CREATE DATABASE {}'.format(params['database'])
database = params["database"]
stmt = "DROP DATABASE {}".format(database)
try:
conn.execute(stmt)
except Exception:
logger.warning('MapD DDL statement %r failed', stmt)

stmt = 'CREATE DATABASE {}'.format(database)
try:
conn.execute(stmt)
except Exception:
Expand All @@ -299,7 +306,7 @@ def mapd(schema, tables, data_directory, **params):
user=params['user'],
password=params['password'],
port=params['port'],
dbname=params['database'],
dbname=database,
)

# create tables
Expand Down Expand Up @@ -343,7 +350,6 @@ def mapd(schema, tables, data_directory, **params):
conn.load_table_columnar(table, df)

conn.close()
logger.info('Done!')


@cli.command()
Expand Down
58 changes: 58 additions & 0 deletions ci/schema/postgresql.sql
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,61 @@ INSERT INTO array_types VALUES
(ARRAY[NULL, 1, NULL], ARRAY[NULL, 'a', NULL], ARRAY[]::DOUBLE PRECISION[], 'b', 4.0),
(ARRAY[2, NULL, 3], ARRAY['b', NULL, 'c'], NULL, 'b', 5.0),
(ARRAY[4, NULL, NULL, 5], ARRAY['d', NULL, NULL, 'e'], ARRAY[4.0, NULL, NULL, 5.0], 'c', 6.0);

DROP TABLE IF EXISTS films CASCADE;

CREATE TABLE IF NOT EXISTS films (
code CHAR(5) PRIMARY KEY,
title VARCHAR(40) NOT NULL,
did INTEGER NOT NULL,
date_prod DATE,
kind VARCHAR(10),
len INTERVAL HOUR TO MINUTE
);

INSERT INTO films VALUES
('A', 'Avengers', 1, DATE '2018-01-01', 'Action', INTERVAL '2 hours 35 minutes'),
('B', 'Ghostbusters', 2, DATE '2018-01-02', 'Ghost', INTERVAL '1 hour 30 minutes');

DROP TABLE IF EXISTS intervals CASCADE;

CREATE TABLE IF NOT EXISTS intervals (
-- enable year and month when relativedelta support lands
-- a INTERVAL YEAR,
-- b INTERVAL MONTH,
c INTERVAL DAY,
d INTERVAL HOUR,
e INTERVAL MINUTE,
f INTERVAL SECOND,
-- g INTERVAL YEAR TO MONTH,
h INTERVAL DAY TO HOUR,
i INTERVAL DAY TO MINUTE,
j INTERVAL DAY TO SECOND,
k INTERVAL HOUR TO MINUTE,
l INTERVAL HOUR TO SECOND,
m INTERVAL MINUTE TO SECOND
);

INSERT INTO intervals VALUES
(
-- '1 year',
-- '1 month',
'1 day',
'1 hour',
'-1 minute',
'1 second 30 milliseconds -10 microseconds',
-- '-1 year 3 months',
'1 day 4 hours',
'1 day 17 minutes',
'-1 day -2 hours 17 minutes 30 seconds',
'1 hour 2 minutes',
'1 hour 2 minutes -7 seconds 37 microseconds',
'1 minute 3 seconds 2 milliseconds 9 microseconds'
);


CREATE TABLE IF NOT EXISTS not_supported_intervals (
a INTERVAL YEAR,
b INTERVAL YEAR,
g INTERVAL YEAR TO MONTH
);
32 changes: 32 additions & 0 deletions ibis/sql/alchemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,38 @@ def sa_double(_, satype, nullable=True):
return dt.Double(nullable=nullable)


POSTGRES_FIELD_TO_IBIS_UNIT = {
"YEAR": "Y",
"MONTH": "M",
"DAY": "D",
"HOUR": "h",
"MINUTE": "m",
"SECOND": "s",
"YEAR TO MONTH": "M",
"DAY TO HOUR": "h",
"DAY TO MINUTE": "m",
"DAY TO SECOND": "s",
"HOUR TO MINUTE": "m",
"HOUR TO SECOND": "s",
"MINUTE TO SECOND": "s",
}


@dt.dtype.register(PostgreSQLDialect, sa.dialects.postgresql.INTERVAL)
def sa_postgres_interval(_, satype, nullable=True):
field = satype.fields.upper()
unit = POSTGRES_FIELD_TO_IBIS_UNIT.get(field, None)
if unit is None:
raise ValueError(
"Unknown PostgreSQL interval field {!r}".format(field)
)
elif unit in {"Y", "M"}:
raise ValueError(
"Variable length timedeltas are not yet supported with PostgreSQL"
)
return dt.Interval(unit=unit, nullable=nullable)


@dt.dtype.register(MySQLDialect, sa.dialects.mysql.DOUBLE)
def sa_mysql_double(_, satype, nullable=True):
# TODO: handle asdecimal=True
Expand Down
5 changes: 5 additions & 0 deletions ibis/sql/postgres/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ def at(alltypes):
return alltypes.op().sqla_table


@pytest.fixture(scope='module')
def intervals(con):
return con.table("intervals")


@pytest.fixture
def translate():
from ibis.sql.postgres.compiler import PostgreSQLDialect
Expand Down
69 changes: 68 additions & 1 deletion ibis/sql/postgres/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,18 @@
# limitations under the License.

import os

import pytest

import numpy as np
import pandas as pd

from ibis.tests.util import assert_equal
import ibis.expr.datatypes as dt
import ibis.expr.types as ir
import ibis

from ibis.tests.util import assert_equal

pytest.importorskip('sqlalchemy')
pytest.importorskip('psycopg2')

Expand Down Expand Up @@ -131,3 +136,65 @@ def test_schema_table():
schema = con.schema('information_schema')

assert isinstance(schema['tables'], ir.TableExpr)


def test_interval_films_schema(con):
t = con.table("films")
assert t.len.type() == dt.Interval(unit="m")
assert t.len.execute().dtype == np.dtype("timedelta64[ns]")


@pytest.mark.parametrize(
("column", "expected_dtype"),
[
# ("a", dt.Interval("Y")),
# ("b", dt.Interval("M")),
("c", dt.Interval("D")),
("d", dt.Interval("h")),
("e", dt.Interval("m")),
("f", dt.Interval("s")),
# ("g", dt.Interval("M")),
("h", dt.Interval("h")),
("i", dt.Interval("m")),
("j", dt.Interval("s")),
("k", dt.Interval("m")),
("l", dt.Interval("s")),
("m", dt.Interval("s")),
],
)
def test_all_interval_types_schema(intervals, column, expected_dtype):
assert intervals[column].type() == expected_dtype


@pytest.mark.parametrize(
("column", "expected_dtype"),
[
# ("a", dt.Interval("Y")),
# ("b", dt.Interval("M")),
("c", dt.Interval("D")),
("d", dt.Interval("h")),
("e", dt.Interval("m")),
("f", dt.Interval("s")),
# ("g", dt.Interval("M")),
("h", dt.Interval("h")),
("i", dt.Interval("m")),
("j", dt.Interval("s")),
("k", dt.Interval("m")),
("l", dt.Interval("s")),
("m", dt.Interval("s")),
],
)
def test_all_interval_types_execute(intervals, column, expected_dtype):
expr = intervals[column]
series = expr.execute()
assert series.dtype == np.dtype("timedelta64[ns]")


@pytest.mark.xfail(
raises=ValueError, reason="Year and month interval types not yet supported"
)
def test_unsupported_intervals(con):
t = con.table("not_supported_intervals")
assert t["a"].type() == dt.Interval("Y")
assert t["b"].type() == dt.Interval("M")
assert t["g"].type() == dt.Interval("M")

0 comments on commit 65bdb04

Please sign in to comment.