Skip to content

Commit aca30e1

Browse files
cpcloudgforsyth
authored andcommitted
fix(mssql): render dates, times and timestamps correctly
1 parent 19e878c commit aca30e1

File tree

5 files changed

+74
-1
lines changed

5 files changed

+74
-1
lines changed

ibis/backends/mssql/registry.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import sqlalchemy as sa
4+
from sqlalchemy.dialects import mssql
45
from sqlalchemy.ext.compiler import compiles
56

67
import ibis.common.exceptions as com
@@ -38,6 +39,13 @@ def mssql_substr(element, compiler, **kw):
3839
return compiler.process(sa.func.substring(*element.clauses), **kw)
3940

4041

42+
@compiles(mssql.VARBINARY)
43+
def compile_mssql_varbinary(element, compiler, **kw):
44+
if (length := element.length) is not None:
45+
return f"VARBINARY({length})"
46+
return "VARBINARY"
47+
48+
4149
# String
4250
# TODO: find is copied from SQLite, we should really have a
4351
# "base" set of SQL functions that are the most common APIs across the major
@@ -162,6 +170,62 @@ def _len(x):
162170
return sa.func.len("A" + x + "Z") - 2
163171

164172

173+
def _literal(_, op):
174+
dtype = op.dtype
175+
value = op.value
176+
177+
if value is None:
178+
return sa.null()
179+
180+
if dtype.is_array():
181+
value = list(value)
182+
elif dtype.is_decimal():
183+
value = value.normalize()
184+
elif dtype.is_date():
185+
return sa.func.datefromparts(value.year, value.month, value.day)
186+
elif dtype.is_timestamp():
187+
args = (
188+
value.year,
189+
value.month,
190+
value.day,
191+
value.hour,
192+
value.minute,
193+
value.second,
194+
value.microsecond,
195+
)
196+
if dtype.timezone is not None:
197+
assert value.tzinfo is not None
198+
199+
offset = value.strftime("%z")
200+
hour_offset = int(offset[:3])
201+
minute_offset = int(offset[-2:])
202+
return sa.func.datetimeoffsetfromparts(
203+
*args,
204+
hour_offset,
205+
minute_offset,
206+
6, # precision
207+
)
208+
else:
209+
return sa.func.datetime2fromparts(
210+
*args,
211+
6, # precision
212+
)
213+
elif dtype.is_time():
214+
return sa.func.timefromparts(
215+
value.hour,
216+
value.minute,
217+
value.second,
218+
value.microsecond,
219+
sa.literal_column("0"),
220+
)
221+
elif dtype.is_uuid():
222+
return sa.cast(sa.literal(str(value)), mssql.UNIQUEIDENTIFIER)
223+
elif dtype.is_binary():
224+
return sa.cast(value, mssql.VARBINARY("max"))
225+
226+
return sa.literal(value)
227+
228+
165229
operation_registry = sqlalchemy_operation_registry.copy()
166230
operation_registry.update(sqlalchemy_window_functions_registry)
167231

@@ -246,7 +310,7 @@ def _len(x):
246310
6,
247311
),
248312
ops.TimeFromHMS: fixed_arity(
249-
lambda h, m, s: sa.func.timefromparts(h, m, s, 0, 0), 3
313+
lambda h, m, s: sa.func.timefromparts(h, m, s, 0, sa.literal_column("0")), 3
250314
),
251315
ops.TimestampTruncate: _timestamp_truncate,
252316
ops.DateTruncate: _timestamp_truncate,
@@ -258,6 +322,7 @@ def _len(x):
258322
ops.TimeDelta: _temporal_delta,
259323
ops.DateDelta: _temporal_delta,
260324
ops.TimestampDelta: _temporal_delta,
325+
ops.Literal: _literal,
261326
}
262327
)
263328

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
SELECT
2+
DATEFROMPARTS(2023, 4, 7) AS "datetime.date(2023, 4, 7)"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
SELECT
2+
DATETIME2FROMPARTS(2023, 4, 7, 4, 5, 6, 230136, 6) AS "datetime.datetime(2023, 4, 7, 4, 5, 6, 230136)"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
SELECT
2+
TIMEFROMPARTS(4, 5, 6, 0, 0) AS "datetime.time(4, 5, 6)"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
SELECT
2+
TIMEFROMPARTS(4, 5, 6, 234567, 0) AS "datetime.time(4, 5, 6, 234567)"

0 commit comments

Comments
 (0)