diff --git a/ibis/backends/mssql/registry.py b/ibis/backends/mssql/registry.py index 27dd595120a2..050b5a5ec947 100644 --- a/ibis/backends/mssql/registry.py +++ b/ibis/backends/mssql/registry.py @@ -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 @@ -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 @@ -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) @@ -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, @@ -258,6 +322,7 @@ def _len(x): ops.TimeDelta: _temporal_delta, ops.DateDelta: _temporal_delta, ops.TimestampDelta: _temporal_delta, + ops.Literal: _literal, } ) diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literals/mssql-date/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literals/mssql-date/out.sql new file mode 100644 index 000000000000..4636132576f6 --- /dev/null +++ b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literals/mssql-date/out.sql @@ -0,0 +1,2 @@ +SELECT + DATEFROMPARTS(2023, 4, 7) AS "datetime.date(2023, 4, 7)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literals/mssql-timestamp/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literals/mssql-timestamp/out.sql new file mode 100644 index 000000000000..e2fe11d84ee9 --- /dev/null +++ b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literals/mssql-timestamp/out.sql @@ -0,0 +1,2 @@ +SELECT + DATETIME2FROMPARTS(2023, 4, 7, 4, 5, 6, 230136, 6) AS "datetime.datetime(2023, 4, 7, 4, 5, 6, 230136)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literals/0-mssql/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literals/0-mssql/out.sql new file mode 100644 index 000000000000..1081342a8f7b --- /dev/null +++ b/ibis/backends/tests/snapshots/test_temporal/test_time_literals/0-mssql/out.sql @@ -0,0 +1,2 @@ +SELECT + TIMEFROMPARTS(4, 5, 6, 0, 0) AS "datetime.time(4, 5, 6)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literals/234567-mssql/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literals/234567-mssql/out.sql new file mode 100644 index 000000000000..bd1d495137b7 --- /dev/null +++ b/ibis/backends/tests/snapshots/test_temporal/test_time_literals/234567-mssql/out.sql @@ -0,0 +1,2 @@ +SELECT + TIMEFROMPARTS(4, 5, 6, 234567, 0) AS "datetime.time(4, 5, 6, 234567)" \ No newline at end of file