Skip to content

Commit

Permalink
fix(mssql): render dates, times and timestamps correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
cpcloud authored and gforsyth committed Dec 12, 2023
1 parent 19e878c commit aca30e1
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 1 deletion.
67 changes: 66 additions & 1 deletion ibis/backends/mssql/registry.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import sqlalchemy as sa
from sqlalchemy.dialects import mssql
from sqlalchemy.ext.compiler import compiles

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


@compiles(mssql.VARBINARY)
def compile_mssql_varbinary(element, compiler, **kw):
if (length := element.length) is not None:
return f"VARBINARY({length})"
return "VARBINARY"


# String
# TODO: find is copied from SQLite, we should really have a
# "base" set of SQL functions that are the most common APIs across the major
Expand Down Expand Up @@ -162,6 +170,62 @@ def _len(x):
return sa.func.len("A" + x + "Z") - 2


def _literal(_, op):
dtype = op.dtype
value = op.value

if value is None:
return sa.null()

if dtype.is_array():
value = list(value)
elif dtype.is_decimal():
value = value.normalize()
elif dtype.is_date():
return sa.func.datefromparts(value.year, value.month, value.day)
elif dtype.is_timestamp():
args = (
value.year,
value.month,
value.day,
value.hour,
value.minute,
value.second,
value.microsecond,
)
if dtype.timezone is not None:
assert value.tzinfo is not None

offset = value.strftime("%z")
hour_offset = int(offset[:3])
minute_offset = int(offset[-2:])
return sa.func.datetimeoffsetfromparts(
*args,
hour_offset,
minute_offset,
6, # precision
)
else:
return sa.func.datetime2fromparts(
*args,
6, # precision
)
elif dtype.is_time():
return sa.func.timefromparts(
value.hour,
value.minute,
value.second,
value.microsecond,
sa.literal_column("0"),
)
elif dtype.is_uuid():
return sa.cast(sa.literal(str(value)), mssql.UNIQUEIDENTIFIER)
elif dtype.is_binary():
return sa.cast(value, mssql.VARBINARY("max"))

return sa.literal(value)


operation_registry = sqlalchemy_operation_registry.copy()
operation_registry.update(sqlalchemy_window_functions_registry)

Expand Down Expand Up @@ -246,7 +310,7 @@ def _len(x):
6,
),
ops.TimeFromHMS: fixed_arity(
lambda h, m, s: sa.func.timefromparts(h, m, s, 0, 0), 3
lambda h, m, s: sa.func.timefromparts(h, m, s, 0, sa.literal_column("0")), 3
),
ops.TimestampTruncate: _timestamp_truncate,
ops.DateTruncate: _timestamp_truncate,
Expand All @@ -258,6 +322,7 @@ def _len(x):
ops.TimeDelta: _temporal_delta,
ops.DateDelta: _temporal_delta,
ops.TimestampDelta: _temporal_delta,
ops.Literal: _literal,
}
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SELECT
DATEFROMPARTS(2023, 4, 7) AS "datetime.date(2023, 4, 7)"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SELECT
DATETIME2FROMPARTS(2023, 4, 7, 4, 5, 6, 230136, 6) AS "datetime.datetime(2023, 4, 7, 4, 5, 6, 230136)"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SELECT
TIMEFROMPARTS(4, 5, 6, 0, 0) AS "datetime.time(4, 5, 6)"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SELECT
TIMEFROMPARTS(4, 5, 6, 234567, 0) AS "datetime.time(4, 5, 6, 234567)"

0 comments on commit aca30e1

Please sign in to comment.