Skip to content

Commit 37d122d

Browse files
committed
DateTime and more: Use CrateDB's DateTime
... instead of `sa.DateTime` and `sa.TIMESTAMP`. Introduce `visit_TIMESTAMP` from PGTypeCompiler to render SQL DDL clauses like `TIMESTAMP WITH|WITHOUT TIME ZONE`.
1 parent 6d4413f commit 37d122d

File tree

4 files changed

+66
-30
lines changed

4 files changed

+66
-30
lines changed

src/sqlalchemy_cratedb/compiler.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ def visit_SMALLINT(self, type_, **kw):
225225
return 'SHORT'
226226

227227
def visit_datetime(self, type_, **kw):
228-
return 'TIMESTAMP'
228+
return self.visit_TIMESTAMP(type_, **kw)
229229

230230
def visit_date(self, type_, **kw):
231231
return 'TIMESTAMP'
@@ -245,6 +245,16 @@ def visit_FLOAT_VECTOR(self, type_, **kw):
245245
raise ValueError("FloatVector must be initialized with dimension size")
246246
return f"FLOAT_VECTOR({dimensions})"
247247

248+
def visit_TIMESTAMP(self, type_, **kw):
249+
"""
250+
Support for `TIMESTAMP WITH|WITHOUT TIME ZONE`.
251+
252+
From `sqlalchemy.dialects.postgresql.base.PGTypeCompiler`.
253+
"""
254+
return "TIMESTAMP %s" % (
255+
(type_.timezone and "WITH" or "WITHOUT") + " TIME ZONE",
256+
)
257+
248258

249259
class CrateCompiler(compiler.SQLCompiler):
250260

src/sqlalchemy_cratedb/dialect.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@
3939
"boolean": sqltypes.Boolean,
4040
"short": sqltypes.SmallInteger,
4141
"smallint": sqltypes.SmallInteger,
42-
"timestamp": sqltypes.TIMESTAMP,
43-
"timestamp with time zone": sqltypes.TIMESTAMP,
42+
"timestamp": sqltypes.TIMESTAMP(timezone=False),
43+
"timestamp with time zone": sqltypes.TIMESTAMP(timezone=True),
4444
"object": ObjectType,
4545
"integer": sqltypes.Integer,
4646
"long": sqltypes.NUMERIC,
@@ -61,8 +61,8 @@
6161
TYPES_MAP["boolean_array"] = ARRAY(sqltypes.Boolean)
6262
TYPES_MAP["short_array"] = ARRAY(sqltypes.SmallInteger)
6363
TYPES_MAP["smallint_array"] = ARRAY(sqltypes.SmallInteger)
64-
TYPES_MAP["timestamp_array"] = ARRAY(sqltypes.TIMESTAMP)
65-
TYPES_MAP["timestamp with time zone_array"] = ARRAY(sqltypes.TIMESTAMP)
64+
TYPES_MAP["timestamp_array"] = ARRAY(sqltypes.TIMESTAMP(timezone=False))
65+
TYPES_MAP["timestamp with time zone_array"] = ARRAY(sqltypes.TIMESTAMP(timezone=True))
6666
TYPES_MAP["long_array"] = ARRAY(sqltypes.NUMERIC)
6767
TYPES_MAP["bigint_array"] = ARRAY(sqltypes.NUMERIC)
6868
TYPES_MAP["double_array"] = ARRAY(sqltypes.DECIMAL)
@@ -147,8 +147,9 @@ def process(value):
147147

148148

149149
colspecs = {
150+
sqltypes.Date: Date,
150151
sqltypes.DateTime: DateTime,
151-
sqltypes.Date: Date
152+
sqltypes.TIMESTAMP: DateTime,
152153
}
153154

154155

tests/create_table_test.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,10 @@ class User(self.Base):
6767
'\n\tlong_col1 LONG, \n\tlong_col2 LONG, '
6868
'\n\tbool_col BOOLEAN, '
6969
'\n\tshort_col SHORT, '
70-
'\n\tdatetime_col TIMESTAMP, \n\tdate_col TIMESTAMP, '
71-
'\n\tfloat_col FLOAT, \n\tdouble_col DOUBLE, '
70+
'\n\tdatetime_col TIMESTAMP WITHOUT TIME ZONE, '
71+
'\n\tdate_col TIMESTAMP, '
72+
'\n\tfloat_col FLOAT, '
73+
'\n\tdouble_col DOUBLE, '
7274
'\n\tPRIMARY KEY (string_col)\n)\n\n'),
7375
())
7476

@@ -286,7 +288,7 @@ class DummyTable(self.Base):
286288
fake_cursor.execute.assert_called_with(
287289
('\nCREATE TABLE t (\n\t'
288290
'pk STRING NOT NULL, \n\t'
289-
'a TIMESTAMP DEFAULT now(), \n\t'
291+
'a TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), \n\t'
290292
'PRIMARY KEY (pk)\n)\n\n'), ())
291293

292294
def test_column_server_default_string(self):
@@ -312,7 +314,7 @@ class DummyTable(self.Base):
312314
fake_cursor.execute.assert_called_with(
313315
('\nCREATE TABLE t (\n\t'
314316
'pk STRING NOT NULL, \n\t'
315-
'a TIMESTAMP DEFAULT now(), \n\t'
317+
'a TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), \n\t'
316318
'PRIMARY KEY (pk)\n)\n\n'), ())
317319

318320
def test_column_server_default_text_constant(self):

tests/datetime_test.py

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
from __future__ import absolute_import
2323

24-
from datetime import datetime, tzinfo, timedelta
24+
from datetime import tzinfo, timedelta
2525
import datetime as dt
2626
from unittest import TestCase, skipIf
2727
from unittest.mock import patch, MagicMock
@@ -31,6 +31,7 @@
3131
from sqlalchemy.orm import Session, sessionmaker
3232

3333
from sqlalchemy_cratedb import SA_VERSION, SA_1_4
34+
from sqlalchemy_cratedb.dialect import DateTime
3435

3536
try:
3637
from sqlalchemy.orm import declarative_base
@@ -57,6 +58,15 @@ def dst(self, date_time):
5758
return timedelta(seconds=-7200)
5859

5960

61+
INPUT_DATE = dt.date(2009, 5, 13)
62+
INPUT_DATETIME_NOTZ = dt.datetime(2009, 5, 13, 19, 19, 30, 123456)
63+
INPUT_DATETIME_TZ = dt.datetime(2009, 5, 13, 19, 19, 30, 123456, tzinfo=CST())
64+
OUTPUT_DATE = INPUT_DATE
65+
OUTPUT_TIME = dt.time(19, 19, 30, 123000)
66+
OUTPUT_DATETIME_NOTZ = dt.datetime(2009, 5, 13, 19, 19, 30, 123000)
67+
OUTPUT_DATETIME_TZ = dt.datetime(2009, 5, 13, 19, 19, 30, 123000)
68+
69+
6070
@skipIf(SA_VERSION < SA_1_4, "SQLAlchemy 1.3 suddenly has problems with these test cases")
6171
@patch('crate.client.connection.Cursor', FakeCursor)
6272
class SqlAlchemyDateAndDateTimeTest(TestCase):
@@ -69,7 +79,7 @@ class Character(Base):
6979
__tablename__ = 'characters'
7080
name = sa.Column(sa.String, primary_key=True)
7181
date = sa.Column(sa.Date)
72-
timestamp = sa.Column(sa.DateTime)
82+
datetime = sa.Column(sa.DateTime)
7383

7484
fake_cursor.description = (
7585
('characters_name', None, None, None, None, None, None),
@@ -91,7 +101,7 @@ def test_date_can_handle_datetime(self):
91101
def test_date_can_handle_tz_aware_datetime(self):
92102
character = self.Character()
93103
character.name = "Athur"
94-
character.timestamp = datetime(2009, 5, 13, 19, 19, 30, tzinfo=CST())
104+
character.datetime = INPUT_DATETIME_NOTZ
95105
self.session.add(character)
96106

97107

@@ -102,7 +112,8 @@ class FooBar(Base):
102112
__tablename__ = "foobar"
103113
name = sa.Column(sa.String, primary_key=True)
104114
date = sa.Column(sa.Date)
105-
datetime = sa.Column(sa.DateTime)
115+
datetime_notz = sa.Column(DateTime(timezone=False))
116+
datetime_tz = sa.Column(DateTime(timezone=True))
106117

107118

108119
@pytest.fixture
@@ -124,22 +135,28 @@ def test_datetime_notz(session):
124135
# Insert record.
125136
foo_item = FooBar(
126137
name="foo",
127-
date=dt.date(2009, 5, 13),
128-
datetime=dt.datetime(2009, 5, 13, 19, 19, 30, 123456),
138+
date=INPUT_DATE,
139+
datetime_notz=INPUT_DATETIME_NOTZ,
140+
datetime_tz=INPUT_DATETIME_NOTZ,
129141
)
130142
session.add(foo_item)
131143
session.commit()
132144
session.execute(sa.text("REFRESH TABLE foobar"))
133145

134146
# Query record.
135-
result = session.execute(sa.select(FooBar.name, FooBar.date, FooBar.datetime)).mappings().first()
147+
result = session.execute(sa.select(
148+
FooBar.name, FooBar.date, FooBar.datetime_notz, FooBar.datetime_tz)).mappings().first()
136149

137150
# Compare outcome.
138-
assert result["date"].year == 2009
139-
assert result["datetime"].year == 2009
140-
assert result["datetime"].tzname() is None
141-
assert result["datetime"].timetz() == dt.time(19, 19, 30, 123000)
142-
assert result["datetime"].tzinfo is None
151+
assert result["date"] == OUTPUT_DATE
152+
assert result["datetime_notz"] == OUTPUT_DATETIME_NOTZ
153+
assert result["datetime_notz"].tzname() is None
154+
assert result["datetime_notz"].timetz() == OUTPUT_TIME
155+
assert result["datetime_notz"].tzinfo is None
156+
assert result["datetime_tz"] == OUTPUT_DATETIME_NOTZ
157+
assert result["datetime_tz"].tzname() is None
158+
assert result["datetime_tz"].timetz() == OUTPUT_TIME
159+
assert result["datetime_tz"].tzinfo is None
143160

144161

145162
@pytest.mark.skipif(SA_VERSION < SA_1_4, reason="Test case not supported on SQLAlchemy 1.3")
@@ -151,19 +168,25 @@ def test_datetime_tz(session):
151168
# Insert record.
152169
foo_item = FooBar(
153170
name="foo",
154-
date=dt.date(2009, 5, 13),
155-
datetime=dt.datetime(2009, 5, 13, 19, 19, 30, 123456, tzinfo=CST()),
171+
date=INPUT_DATE,
172+
datetime_notz=INPUT_DATETIME_TZ,
173+
datetime_tz=INPUT_DATETIME_TZ,
156174
)
157175
session.add(foo_item)
158176
session.commit()
159177
session.execute(sa.text("REFRESH TABLE foobar"))
160178

161179
# Query record.
162-
result = session.execute(sa.select(FooBar.name, FooBar.date, FooBar.datetime)).mappings().first()
180+
result = session.execute(sa.select(
181+
FooBar.name, FooBar.date, FooBar.datetime_notz, FooBar.datetime_tz)).mappings().first()
163182

164183
# Compare outcome.
165-
assert result["date"].year == 2009
166-
assert result["datetime"].year == 2009
167-
assert result["datetime"].tzname() is None
168-
assert result["datetime"].timetz() == dt.time(19, 19, 30, 123000)
169-
assert result["datetime"].tzinfo is None
184+
assert result["date"] == OUTPUT_DATE
185+
assert result["datetime_notz"] == OUTPUT_DATETIME_TZ
186+
assert result["datetime_notz"].tzname() is None
187+
assert result["datetime_notz"].timetz() == OUTPUT_TIME
188+
assert result["datetime_notz"].tzinfo is None
189+
assert result["datetime_tz"] == OUTPUT_DATETIME_TZ
190+
assert result["datetime_tz"].tzname() is None
191+
assert result["datetime_tz"].timetz() == OUTPUT_TIME
192+
assert result["datetime_tz"].tzinfo is None

0 commit comments

Comments
 (0)