From 1609a18a27d7d1039e34dfad3a0afaef4550de73 Mon Sep 17 00:00:00 2001 From: Lulzim Bilali Date: Thu, 13 Nov 2025 13:38:32 +0100 Subject: [PATCH 1/9] Feat(tsql)!: support `DATEDIFF_BIG` (#6323) Co-authored-by: Lulzim Bilali --- sqlglot/dialects/tsql.py | 15 ++++- sqlglot/expressions.py | 2 +- sqlglot/typing/__init__.py | 6 +- tests/dialects/test_tsql.py | 108 ++++++++++++++++++------------------ 4 files changed, 73 insertions(+), 58 deletions(-) diff --git a/sqlglot/dialects/tsql.py b/sqlglot/dialects/tsql.py index 5e897dbe2d..251f689219 100644 --- a/sqlglot/dialects/tsql.py +++ b/sqlglot/dialects/tsql.py @@ -244,7 +244,7 @@ def _string_agg_sql(self: TSQL.Generator, expression: exp.GroupConcat) -> str: def _build_date_delta( - exp_class: t.Type[E], unit_mapping: t.Optional[t.Dict[str, str]] = None + exp_class: t.Type[E], unit_mapping: t.Optional[t.Dict[str, str]] = None, big_int: bool = False ) -> t.Callable[[t.List], E]: def _builder(args: t.List) -> E: unit = seq_get(args, 0) @@ -260,12 +260,15 @@ def _builder(args: t.List) -> E: else: # We currently don't handle float values, i.e. they're not converted to equivalent DATETIMEs. # This is not a problem when generating T-SQL code, it is when transpiling to other dialects. - return exp_class(this=seq_get(args, 2), expression=start_date, unit=unit) + return exp_class( + this=seq_get(args, 2), expression=start_date, unit=unit, big_int=big_int + ) return exp_class( this=exp.TimeStrToTime(this=seq_get(args, 2)), expression=exp.TimeStrToTime(this=start_date), unit=unit, + big_int=big_int, ) return _builder @@ -597,6 +600,9 @@ class Parser(parser.Parser): ), "DATEADD": build_date_delta(exp.DateAdd, unit_mapping=DATE_DELTA_INTERVAL), "DATEDIFF": _build_date_delta(exp.DateDiff, unit_mapping=DATE_DELTA_INTERVAL), + "DATEDIFF_BIG": _build_date_delta( + exp.DateDiff, unit_mapping=DATE_DELTA_INTERVAL, big_int=True + ), "DATENAME": _build_formatted_time(exp.TimeToStr, full_format_mapping=True), "DATEPART": _build_formatted_time(exp.TimeToStr), "DATETIMEFROMPARTS": _build_datetimefromparts, @@ -1033,7 +1039,6 @@ class Generator(generator.Generator): exp.AutoIncrementColumnConstraint: lambda *_: "IDENTITY", exp.Chr: rename_func("CHAR"), exp.DateAdd: date_delta_sql("DATEADD"), - exp.DateDiff: date_delta_sql("DATEDIFF"), exp.CTE: transforms.preprocess([qualify_derived_table_outputs]), exp.CurrentDate: rename_func("GETDATE"), exp.CurrentTimestamp: rename_func("GETDATE"), @@ -1299,6 +1304,10 @@ def count_sql(self, expression: exp.Count) -> str: func_name = "COUNT_BIG" if expression.args.get("big_int") else "COUNT" return rename_func(func_name)(self, expression) + def datediff_sql(self, expression: exp.DateDiff) -> str: + func_name = "DATEDIFF_BIG" if expression.args.get("big_int") else "DATEDIFF" + return date_delta_sql(func_name)(self, expression) + def offset_sql(self, expression: exp.Offset) -> str: return f"{super().offset_sql(expression)} ROWS" diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py index c09adbfb65..372501b576 100644 --- a/sqlglot/expressions.py +++ b/sqlglot/expressions.py @@ -6267,7 +6267,7 @@ class DateSub(Func, IntervalOp): class DateDiff(Func, TimeUnit): _sql_names = ["DATEDIFF", "DATE_DIFF"] - arg_types = {"this": True, "expression": True, "unit": False, "zone": False} + arg_types = {"this": True, "expression": True, "unit": False, "zone": False, "big_int": False} class DateTrunc(Func): diff --git a/sqlglot/typing/__init__.py b/sqlglot/typing/__init__.py index 6aab788a8a..b216590d55 100644 --- a/sqlglot/typing/__init__.py +++ b/sqlglot/typing/__init__.py @@ -111,7 +111,6 @@ exp.Ascii, exp.Ceil, exp.DatetimeDiff, - exp.DateDiff, exp.TimestampDiff, exp.TimeDiff, exp.Unicode, @@ -273,6 +272,11 @@ e, exp.DataType.Type.BIGINT if e.args.get("big_int") else exp.DataType.Type.INT ) }, + exp.DateDiff: { + "annotator": lambda self, e: self._annotate_with_type( + e, exp.DataType.Type.BIGINT if e.args.get("big_int") else exp.DataType.Type.INT + ) + }, exp.DataType: {"annotator": lambda self, e: self._annotate_with_type(e, e.copy())}, exp.Div: {"annotator": lambda self, e: self._annotate_div(e)}, exp.Distinct: {"annotator": lambda self, e: self._annotate_by_args(e, "expressions")}, diff --git a/tests/dialects/test_tsql.py b/tests/dialects/test_tsql.py index 683bd4da8e..afecf99e56 100644 --- a/tests/dialects/test_tsql.py +++ b/tests/dialects/test_tsql.py @@ -1784,62 +1784,64 @@ def test_add_date(self): def test_date_diff(self): self.validate_identity("SELECT DATEDIFF(HOUR, 1.5, '2021-01-01')") + self.validate_identity("SELECT DATEDIFF_BIG(HOUR, 1.5, '2021-01-01')") - self.validate_all( - "SELECT DATEDIFF(quarter, 0, '2021-01-01')", - write={ - "tsql": "SELECT DATEDIFF(QUARTER, CAST('1900-01-01' AS DATETIME2), CAST('2021-01-01' AS DATETIME2))", - "spark": "SELECT DATEDIFF(QUARTER, CAST('1900-01-01' AS TIMESTAMP), CAST('2021-01-01' AS TIMESTAMP))", - "duckdb": "SELECT DATE_DIFF('QUARTER', CAST('1900-01-01' AS TIMESTAMP), CAST('2021-01-01' AS TIMESTAMP))", - }, - ) - self.validate_all( - "SELECT DATEDIFF(day, 1, '2021-01-01')", - write={ - "tsql": "SELECT DATEDIFF(DAY, CAST('1900-01-02' AS DATETIME2), CAST('2021-01-01' AS DATETIME2))", - "spark": "SELECT DATEDIFF(DAY, CAST('1900-01-02' AS TIMESTAMP), CAST('2021-01-01' AS TIMESTAMP))", - "duckdb": "SELECT DATE_DIFF('DAY', CAST('1900-01-02' AS TIMESTAMP), CAST('2021-01-01' AS TIMESTAMP))", - }, - ) - self.validate_all( - "SELECT DATEDIFF(year, '2020-01-01', '2021-01-01')", - write={ - "tsql": "SELECT DATEDIFF(YEAR, CAST('2020-01-01' AS DATETIME2), CAST('2021-01-01' AS DATETIME2))", - "spark": "SELECT DATEDIFF(YEAR, CAST('2020-01-01' AS TIMESTAMP), CAST('2021-01-01' AS TIMESTAMP))", - "spark2": "SELECT CAST(MONTHS_BETWEEN(CAST('2021-01-01' AS TIMESTAMP), CAST('2020-01-01' AS TIMESTAMP)) / 12 AS INT)", - }, - ) - self.validate_all( - "SELECT DATEDIFF(mm, 'start', 'end')", - write={ - "databricks": "SELECT DATEDIFF(MONTH, CAST('start' AS TIMESTAMP), CAST('end' AS TIMESTAMP))", - "spark2": "SELECT CAST(MONTHS_BETWEEN(CAST('end' AS TIMESTAMP), CAST('start' AS TIMESTAMP)) AS INT)", - "tsql": "SELECT DATEDIFF(MONTH, CAST('start' AS DATETIME2), CAST('end' AS DATETIME2))", - }, - ) - self.validate_all( - "SELECT DATEDIFF(quarter, 'start', 'end')", - write={ - "databricks": "SELECT DATEDIFF(QUARTER, CAST('start' AS TIMESTAMP), CAST('end' AS TIMESTAMP))", - "spark": "SELECT DATEDIFF(QUARTER, CAST('start' AS TIMESTAMP), CAST('end' AS TIMESTAMP))", - "spark2": "SELECT CAST(MONTHS_BETWEEN(CAST('end' AS TIMESTAMP), CAST('start' AS TIMESTAMP)) / 3 AS INT)", - "tsql": "SELECT DATEDIFF(QUARTER, CAST('start' AS DATETIME2), CAST('end' AS DATETIME2))", - }, - ) + for fnc in ["DATEDIFF", "DATEDIFF_BIG"]: + self.validate_all( + f"SELECT {fnc}(quarter, 0, '2021-01-01')", + write={ + "tsql": f"SELECT {fnc}(QUARTER, CAST('1900-01-01' AS DATETIME2), CAST('2021-01-01' AS DATETIME2))", + "spark": "SELECT DATEDIFF(QUARTER, CAST('1900-01-01' AS TIMESTAMP), CAST('2021-01-01' AS TIMESTAMP))", + "duckdb": "SELECT DATE_DIFF('QUARTER', CAST('1900-01-01' AS TIMESTAMP), CAST('2021-01-01' AS TIMESTAMP))", + }, + ) + self.validate_all( + f"SELECT {fnc}(day, 1, '2021-01-01')", + write={ + "tsql": f"SELECT {fnc}(DAY, CAST('1900-01-02' AS DATETIME2), CAST('2021-01-01' AS DATETIME2))", + "spark": "SELECT DATEDIFF(DAY, CAST('1900-01-02' AS TIMESTAMP), CAST('2021-01-01' AS TIMESTAMP))", + "duckdb": "SELECT DATE_DIFF('DAY', CAST('1900-01-02' AS TIMESTAMP), CAST('2021-01-01' AS TIMESTAMP))", + }, + ) + self.validate_all( + f"SELECT {fnc}(year, '2020-01-01', '2021-01-01')", + write={ + "tsql": f"SELECT {fnc}(YEAR, CAST('2020-01-01' AS DATETIME2), CAST('2021-01-01' AS DATETIME2))", + "spark": "SELECT DATEDIFF(YEAR, CAST('2020-01-01' AS TIMESTAMP), CAST('2021-01-01' AS TIMESTAMP))", + "spark2": "SELECT CAST(MONTHS_BETWEEN(CAST('2021-01-01' AS TIMESTAMP), CAST('2020-01-01' AS TIMESTAMP)) / 12 AS INT)", + }, + ) + self.validate_all( + f"SELECT {fnc}(mm, 'start', 'end')", + write={ + "databricks": "SELECT DATEDIFF(MONTH, CAST('start' AS TIMESTAMP), CAST('end' AS TIMESTAMP))", + "spark2": "SELECT CAST(MONTHS_BETWEEN(CAST('end' AS TIMESTAMP), CAST('start' AS TIMESTAMP)) AS INT)", + "tsql": f"SELECT {fnc}(MONTH, CAST('start' AS DATETIME2), CAST('end' AS DATETIME2))", + }, + ) + self.validate_all( + f"SELECT {fnc}(quarter, 'start', 'end')", + write={ + "databricks": "SELECT DATEDIFF(QUARTER, CAST('start' AS TIMESTAMP), CAST('end' AS TIMESTAMP))", + "spark": "SELECT DATEDIFF(QUARTER, CAST('start' AS TIMESTAMP), CAST('end' AS TIMESTAMP))", + "spark2": "SELECT CAST(MONTHS_BETWEEN(CAST('end' AS TIMESTAMP), CAST('start' AS TIMESTAMP)) / 3 AS INT)", + "tsql": f"SELECT {fnc}(QUARTER, CAST('start' AS DATETIME2), CAST('end' AS DATETIME2))", + }, + ) - # Check superfluous casts arent added. ref: https://github.com/TobikoData/sqlmesh/issues/2672 - self.validate_all( - "SELECT DATEDIFF(DAY, CAST(a AS DATETIME2), CAST(b AS DATETIME2)) AS x FROM foo", - write={ - "tsql": "SELECT DATEDIFF(DAY, CAST(a AS DATETIME2), CAST(b AS DATETIME2)) AS x FROM foo", - "clickhouse": "SELECT DATE_DIFF(DAY, CAST(CAST(a AS Nullable(DateTime)) AS DateTime64(6)), CAST(CAST(b AS Nullable(DateTime)) AS DateTime64(6))) AS x FROM foo", - }, - ) + # Check superfluous casts arent added. ref: https://github.com/TobikoData/sqlmesh/issues/2672 + self.validate_all( + f"SELECT {fnc}(DAY, CAST(a AS DATETIME2), CAST(b AS DATETIME2)) AS x FROM foo", + write={ + "tsql": f"SELECT {fnc}(DAY, CAST(a AS DATETIME2), CAST(b AS DATETIME2)) AS x FROM foo", + "clickhouse": "SELECT DATE_DIFF(DAY, CAST(CAST(a AS Nullable(DateTime)) AS DateTime64(6)), CAST(CAST(b AS Nullable(DateTime)) AS DateTime64(6))) AS x FROM foo", + }, + ) - self.validate_identity( - "SELECT DATEADD(DAY, DATEDIFF(DAY, -3, GETDATE()), '08:00:00')", - "SELECT DATEADD(DAY, DATEDIFF(DAY, CAST('1899-12-29' AS DATETIME2), CAST(GETDATE() AS DATETIME2)), '08:00:00')", - ) + self.validate_identity( + f"SELECT DATEADD(DAY, {fnc}(DAY, -3, GETDATE()), '08:00:00')", + f"SELECT DATEADD(DAY, {fnc}(DAY, CAST('1899-12-29' AS DATETIME2), CAST(GETDATE() AS DATETIME2)), '08:00:00')", + ) def test_lateral_subquery(self): self.validate_all( From 7a6df9e8fadffd235ab1b46929dd0e3ee8a348e0 Mon Sep 17 00:00:00 2001 From: Lulzim Bilali Date: Thu, 13 Nov 2025 15:09:33 +0100 Subject: [PATCH 2/9] DATEPART and FORMAT are decoupled DATEPART works for all of tsql datepart options and abbreviations --- sqlglot/dialects/tsql.py | 54 ++++++++++++++++++++++++++++++------- tests/dialects/test_tsql.py | 37 +++++++++++++------------ 2 files changed, 64 insertions(+), 27 deletions(-) diff --git a/sqlglot/dialects/tsql.py b/sqlglot/dialects/tsql.py index 251f689219..aa111038c0 100644 --- a/sqlglot/dialects/tsql.py +++ b/sqlglot/dialects/tsql.py @@ -20,6 +20,7 @@ strposition_sql, timestrtotime_sql, trim_sql, + map_date_part, ) from sqlglot.helper import seq_get from sqlglot.parser import build_coalesce @@ -201,20 +202,12 @@ def _build_hashbytes(args: t.List) -> exp.Expression: return exp.func("HASHBYTES", *args) -DATEPART_ONLY_FORMATS = {"DW", "WK", "HOUR", "QUARTER", "ISO_WEEK"} - - def _format_sql(self: TSQL.Generator, expression: exp.NumberToStr | exp.TimeToStr) -> str: fmt = expression.args["format"] if not isinstance(expression, exp.NumberToStr): if fmt.is_string: mapped_fmt = format_time(fmt.name, TSQL.INVERSE_TIME_MAPPING) - - name = (mapped_fmt or "").upper() - if name in DATEPART_ONLY_FORMATS: - return self.func("DATEPART", name, expression.this) - fmt_sql = self.sql(exp.Literal.string(mapped_fmt)) else: fmt_sql = self.format_time(expression) or self.sql(fmt) @@ -224,6 +217,19 @@ def _format_sql(self: TSQL.Generator, expression: exp.NumberToStr | exp.TimeToSt return self.func("FORMAT", expression.this, fmt_sql, expression.args.get("culture")) +def _build_datepart(self: TSQL.Generator, expression: exp.Extract) -> str: + DATE_PART_UNMAPPING = { + "WEEKISO": "ISO_WEEK", + "DAYOFWEEK": "WEEKDAY", + "TIMEZONE_MINUTE": "TZOFFSET", + } + + part = expression.this + name = DATE_PART_UNMAPPING.get(part.name.upper()) or part + + return self.func("DATEPART", name, expression.expression) + + def _string_agg_sql(self: TSQL.Generator, expression: exp.GroupConcat) -> str: this = expression.this distinct = expression.find(exp.Distinct) @@ -418,6 +424,22 @@ class TSQL(Dialect): EXPRESSION_METADATA = EXPRESSION_METADATA.copy() + DATE_PART_MAPPING = { + **Dialect.DATE_PART_MAPPING, + "QQ": "QUARTER", + "M": "MONTH", + "Y": "DAYOFYEAR", + "WW": "WEEK", + "N": "MINUTE", + "SS": "SECOND", + "MCS": "MICROSECOND", + "TZOFFSET": "TIMEZONE_MINUTE", + "TZ": "TIMEZONE_MINUTE", + "ISO_WEEK": "WEEKISO", + "ISOWK": "WEEKISO", + "ISOWW": "WEEKISO", + } + TIME_MAPPING = { "year": "%Y", "dayofyear": "%j", @@ -604,7 +626,6 @@ class Parser(parser.Parser): exp.DateDiff, unit_mapping=DATE_DELTA_INTERVAL, big_int=True ), "DATENAME": _build_formatted_time(exp.TimeToStr, full_format_mapping=True), - "DATEPART": _build_formatted_time(exp.TimeToStr), "DATETIMEFROMPARTS": _build_datetimefromparts, "EOMONTH": _build_eomonth, "FORMAT": _build_format, @@ -670,6 +691,7 @@ class Parser(parser.Parser): order=self._parse_order(), null_handling=self._parse_on_handling("NULL", "NULL", "ABSENT"), ), + "DATEPART": lambda self: self._parse_datepart(), } # The DCOLON (::) operator serves as a scope resolution (exp.ScopeResolution) operator in T-SQL @@ -688,6 +710,18 @@ class Parser(parser.Parser): "ts": exp.Timestamp, } + def _parse_datepart(self) -> exp.Expression: + this = self._parse_var() + expression = self._match(TokenType.COMMA) and self._parse_bitwise() + name = map_date_part(this, self.dialect) + type_name = "TIMESTAMP" + + if isinstance(name, exp.Expression) and name.name == "TIMEZONE_MINUTE": + type_name = "TIMESTAMPTZ" + + expr = self.expression(exp.Cast, this=expression, to=exp.DataType.build(type_name)) + return self.expression(exp.Extract, this=name, expression=expr) + def _parse_alter_table_set(self) -> exp.AlterSet: return self._parse_wrapped(super()._parse_alter_table_set) @@ -1044,7 +1078,7 @@ class Generator(generator.Generator): exp.CurrentTimestamp: rename_func("GETDATE"), exp.CurrentTimestampLTZ: rename_func("SYSDATETIMEOFFSET"), exp.DateStrToDate: datestrtodate_sql, - exp.Extract: rename_func("DATEPART"), + exp.Extract: _build_datepart, exp.GeneratedAsIdentityColumnConstraint: generatedasidentitycolumnconstraint_sql, exp.GroupConcat: _string_agg_sql, exp.If: rename_func("IIF"), diff --git a/tests/dialects/test_tsql.py b/tests/dialects/test_tsql.py index afecf99e56..7ab5656c48 100644 --- a/tests/dialects/test_tsql.py +++ b/tests/dialects/test_tsql.py @@ -1497,7 +1497,7 @@ def test_datepart(self): ) self.validate_identity( "DATEPART(YEAR, x)", - "FORMAT(CAST(x AS DATETIME2), 'yyyy')", + "DATEPART(YEAR, CAST(x AS DATETIME2))", ) self.validate_identity( "DATEPART(HOUR, date_and_time)", @@ -1505,19 +1505,22 @@ def test_datepart(self): ) self.validate_identity( "DATEPART(WEEKDAY, date_and_time)", - "DATEPART(DW, CAST(date_and_time AS DATETIME2))", + "DATEPART(WEEKDAY, CAST(date_and_time AS DATETIME2))", + ) + self.validate_identity( + "DATEPART(tz, date_and_time)", + "DATEPART(TZOFFSET, CAST(date_and_time AS DATETIMEOFFSET))", ) self.validate_identity( "DATEPART(DW, date_and_time)", - "DATEPART(DW, CAST(date_and_time AS DATETIME2))", + "DATEPART(WEEKDAY, CAST(date_and_time AS DATETIME2))", ) - self.validate_all( "SELECT DATEPART(month,'1970-01-01')", write={ - "postgres": "SELECT TO_CHAR(CAST('1970-01-01' AS TIMESTAMP), 'MM')", - "spark": "SELECT DATE_FORMAT(CAST('1970-01-01' AS TIMESTAMP), 'MM')", - "tsql": "SELECT FORMAT(CAST('1970-01-01' AS DATETIME2), 'MM')", + "postgres": "SELECT EXTRACT(month FROM CAST('1970-01-01' AS TIMESTAMP))", + "spark": "SELECT EXTRACT(month FROM CAST('1970-01-01' AS TIMESTAMP))", + "tsql": "SELECT DATEPART(month, CAST('1970-01-01' AS DATETIME2))", }, ) self.validate_all( @@ -1526,9 +1529,9 @@ def test_datepart(self): "postgres": "SELECT DATE_PART('YEAR', '2017-01-01'::DATE)", }, write={ - "postgres": "SELECT TO_CHAR(CAST(CAST('2017-01-01' AS DATE) AS TIMESTAMP), 'YYYY')", - "spark": "SELECT DATE_FORMAT(CAST(CAST('2017-01-01' AS DATE) AS TIMESTAMP), 'yyyy')", - "tsql": "SELECT FORMAT(CAST(CAST('2017-01-01' AS DATE) AS DATETIME2), 'yyyy')", + "postgres": "SELECT EXTRACT(YEAR FROM CAST(CAST('2017-01-01' AS DATE) AS TIMESTAMP))", + "spark": "SELECT EXTRACT(YEAR FROM CAST(CAST('2017-01-01' AS DATE) AS TIMESTAMP))", + "tsql": "SELECT DATEPART(YEAR, CAST(CAST('2017-01-01' AS DATE) AS DATETIME2))", }, ) self.validate_all( @@ -1537,9 +1540,9 @@ def test_datepart(self): "postgres": "SELECT DATE_PART('month', '2017-03-01'::DATE)", }, write={ - "postgres": "SELECT TO_CHAR(CAST(CAST('2017-03-01' AS DATE) AS TIMESTAMP), 'MM')", - "spark": "SELECT DATE_FORMAT(CAST(CAST('2017-03-01' AS DATE) AS TIMESTAMP), 'MM')", - "tsql": "SELECT FORMAT(CAST(CAST('2017-03-01' AS DATE) AS DATETIME2), 'MM')", + "postgres": "SELECT EXTRACT(month FROM CAST(CAST('2017-03-01' AS DATE) AS TIMESTAMP))", + "spark": "SELECT EXTRACT(month FROM CAST(CAST('2017-03-01' AS DATE) AS TIMESTAMP))", + "tsql": "SELECT DATEPART(month, CAST(CAST('2017-03-01' AS DATE) AS DATETIME2))", }, ) self.validate_all( @@ -1548,16 +1551,16 @@ def test_datepart(self): "postgres": "SELECT DATE_PART('day', '2017-01-02'::DATE)", }, write={ - "postgres": "SELECT TO_CHAR(CAST(CAST('2017-01-02' AS DATE) AS TIMESTAMP), 'DD')", - "spark": "SELECT DATE_FORMAT(CAST(CAST('2017-01-02' AS DATE) AS TIMESTAMP), 'dd')", - "tsql": "SELECT FORMAT(CAST(CAST('2017-01-02' AS DATE) AS DATETIME2), 'dd')", + "postgres": "SELECT EXTRACT(day FROM CAST(CAST('2017-01-02' AS DATE) AS TIMESTAMP))", + "spark": "SELECT EXTRACT(day FROM CAST(CAST('2017-01-02' AS DATE) AS TIMESTAMP))", + "tsql": "SELECT DATEPART(day, CAST(CAST('2017-01-02' AS DATE) AS DATETIME2))", }, ) for fmt in ("WEEK", "WW", "WK"): self.validate_identity( f"SELECT DATEPART({fmt}, '2024-11-21')", - "SELECT DATEPART(WK, CAST('2024-11-21' AS DATETIME2))", + "SELECT DATEPART(WEEK, CAST('2024-11-21' AS DATETIME2))", ) for fmt in ("ISOWK", "ISOWW", "ISO_WEEK"): From 6619738c4eb212154fb554d29eb4ddad8e6794c7 Mon Sep 17 00:00:00 2001 From: Lulzim Bilali Date: Mon, 17 Nov 2025 12:18:26 +0100 Subject: [PATCH 3/9] move _build_datepart inside the generator and use extract_sql --- sqlglot/dialects/tsql.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/sqlglot/dialects/tsql.py b/sqlglot/dialects/tsql.py index aa111038c0..c53512a350 100644 --- a/sqlglot/dialects/tsql.py +++ b/sqlglot/dialects/tsql.py @@ -217,19 +217,6 @@ def _format_sql(self: TSQL.Generator, expression: exp.NumberToStr | exp.TimeToSt return self.func("FORMAT", expression.this, fmt_sql, expression.args.get("culture")) -def _build_datepart(self: TSQL.Generator, expression: exp.Extract) -> str: - DATE_PART_UNMAPPING = { - "WEEKISO": "ISO_WEEK", - "DAYOFWEEK": "WEEKDAY", - "TIMEZONE_MINUTE": "TZOFFSET", - } - - part = expression.this - name = DATE_PART_UNMAPPING.get(part.name.upper()) or part - - return self.func("DATEPART", name, expression.expression) - - def _string_agg_sql(self: TSQL.Generator, expression: exp.GroupConcat) -> str: this = expression.this distinct = expression.find(exp.Distinct) @@ -710,7 +697,7 @@ class Parser(parser.Parser): "ts": exp.Timestamp, } - def _parse_datepart(self) -> exp.Expression: + def _parse_datepart(self) -> exp.Extract: this = self._parse_var() expression = self._match(TokenType.COMMA) and self._parse_bitwise() name = map_date_part(this, self.dialect) @@ -1078,7 +1065,6 @@ class Generator(generator.Generator): exp.CurrentTimestamp: rename_func("GETDATE"), exp.CurrentTimestampLTZ: rename_func("SYSDATETIMEOFFSET"), exp.DateStrToDate: datestrtodate_sql, - exp.Extract: _build_datepart, exp.GeneratedAsIdentityColumnConstraint: generatedasidentitycolumnconstraint_sql, exp.GroupConcat: _string_agg_sql, exp.If: rename_func("IIF"), @@ -1200,6 +1186,18 @@ def splitpart_sql(self: TSQL.Generator, expression: exp.SplitPart) -> str: "PARSENAME", this, exp.Literal.number(split_count + 1 - part_index.to_py()) ) + def extract_sql(self, expression: exp.Extract) -> str: + DATE_PART_UNMAPPING = { + "WEEKISO": "ISO_WEEK", + "DAYOFWEEK": "WEEKDAY", + "TIMEZONE_MINUTE": "TZOFFSET", + } + + part = expression.this + name = DATE_PART_UNMAPPING.get(part.name.upper()) or part + + return self.func("DATEPART", name, expression.expression) + def timefromparts_sql(self, expression: exp.TimeFromParts) -> str: nano = expression.args.get("nano") if nano is not None: From 976ca9458576a407f7240190b4828a6a774aa074 Mon Sep 17 00:00:00 2001 From: Lulzim Bilali Date: Mon, 17 Nov 2025 12:40:08 +0100 Subject: [PATCH 4/9] move DATE_PART_UNMAPPING to global scope --- sqlglot/dialects/tsql.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sqlglot/dialects/tsql.py b/sqlglot/dialects/tsql.py index c53512a350..0a66180fba 100644 --- a/sqlglot/dialects/tsql.py +++ b/sqlglot/dialects/tsql.py @@ -58,6 +58,11 @@ "d": "day", } +DATE_PART_UNMAPPING = { + "WEEKISO": "ISO_WEEK", + "DAYOFWEEK": "WEEKDAY", + "TIMEZONE_MINUTE": "TZOFFSET", +} DATE_FMT_RE = re.compile("([dD]{1,2})|([mM]{1,2})|([yY]{1,4})|([hH]{1,2})|([sS]{1,2})") @@ -1187,11 +1192,7 @@ def splitpart_sql(self: TSQL.Generator, expression: exp.SplitPart) -> str: ) def extract_sql(self, expression: exp.Extract) -> str: - DATE_PART_UNMAPPING = { - "WEEKISO": "ISO_WEEK", - "DAYOFWEEK": "WEEKDAY", - "TIMEZONE_MINUTE": "TZOFFSET", - } + part = expression.this name = DATE_PART_UNMAPPING.get(part.name.upper()) or part From e30cbf66adcd21a23b1e2e6d35a3a8be101cc037 Mon Sep 17 00:00:00 2001 From: Lulzim Bilali Date: Mon, 17 Nov 2025 12:41:24 +0100 Subject: [PATCH 5/9] remove empty lines --- sqlglot/dialects/tsql.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/sqlglot/dialects/tsql.py b/sqlglot/dialects/tsql.py index 0a66180fba..408af22458 100644 --- a/sqlglot/dialects/tsql.py +++ b/sqlglot/dialects/tsql.py @@ -1192,8 +1192,6 @@ def splitpart_sql(self: TSQL.Generator, expression: exp.SplitPart) -> str: ) def extract_sql(self, expression: exp.Extract) -> str: - - part = expression.this name = DATE_PART_UNMAPPING.get(part.name.upper()) or part From bf6e5dcd252c4f20c1d728bab00dc294cc7ef1ad Mon Sep 17 00:00:00 2001 From: Lulzim Bilali Date: Mon, 17 Nov 2025 13:43:32 +0100 Subject: [PATCH 6/9] remove casting and add tests for all abbreviations --- sqlglot/dialects/tsql.py | 7 +-- tests/dialects/test_tsql.py | 117 ++++++++++++++++++++++++------------ 2 files changed, 80 insertions(+), 44 deletions(-) diff --git a/sqlglot/dialects/tsql.py b/sqlglot/dialects/tsql.py index 408af22458..081cb05af8 100644 --- a/sqlglot/dialects/tsql.py +++ b/sqlglot/dialects/tsql.py @@ -706,13 +706,8 @@ def _parse_datepart(self) -> exp.Extract: this = self._parse_var() expression = self._match(TokenType.COMMA) and self._parse_bitwise() name = map_date_part(this, self.dialect) - type_name = "TIMESTAMP" - if isinstance(name, exp.Expression) and name.name == "TIMEZONE_MINUTE": - type_name = "TIMESTAMPTZ" - - expr = self.expression(exp.Cast, this=expression, to=exp.DataType.build(type_name)) - return self.expression(exp.Extract, this=name, expression=expr) + return self.expression(exp.Extract, this=name, expression=expression) def _parse_alter_table_set(self) -> exp.AlterSet: return self._parse_wrapped(super()._parse_alter_table_set) diff --git a/tests/dialects/test_tsql.py b/tests/dialects/test_tsql.py index 6055f6a452..86c7c71e62 100644 --- a/tests/dialects/test_tsql.py +++ b/tests/dialects/test_tsql.py @@ -1491,36 +1491,77 @@ def test_datename(self): ) def test_datepart(self): - self.validate_identity( - "DATEPART(QUARTER, x)", - "DATEPART(QUARTER, CAST(x AS DATETIME2))", - ) - self.validate_identity( - "DATEPART(YEAR, x)", - "DATEPART(YEAR, CAST(x AS DATETIME2))", - ) - self.validate_identity( - "DATEPART(HOUR, date_and_time)", - "DATEPART(HOUR, CAST(date_and_time AS DATETIME2))", - ) - self.validate_identity( - "DATEPART(WEEKDAY, date_and_time)", - "DATEPART(WEEKDAY, CAST(date_and_time AS DATETIME2))", - ) - self.validate_identity( - "DATEPART(tz, date_and_time)", - "DATEPART(TZOFFSET, CAST(date_and_time AS DATETIMEOFFSET))", - ) - self.validate_identity( - "DATEPART(DW, date_and_time)", - "DATEPART(WEEKDAY, CAST(date_and_time AS DATETIME2))", - ) + for fmt in ("QUARTER", "qq", "q"): + self.validate_identity( + f"DATEPART({fmt}, x)", + "DATEPART(QUARTER, x)", + ) + for fmt in ("YEAR", "yy", "yyyy"): + self.validate_identity( + f"DATEPART({fmt}, x)", + "DATEPART(YEAR, x)", + ) + for fmt in ("HOUR", "hh"): + self.validate_identity( + f"DATEPART({fmt}, date_and_time)", + "DATEPART(HOUR, date_and_time)", + ) + for fmt in ("MINUTE", "mi", "n"): + self.validate_identity( + f"DATEPART({fmt}, date_and_time)", + "DATEPART(MINUTE, date_and_time)", + ) + for fmt in ("SECOND", "ss", "s"): + self.validate_identity( + f"DATEPART({fmt}, date_and_time)", + "DATEPART(SECOND, date_and_time)", + ) + for fmt in ("MILLISECOND", "ms"): + self.validate_identity( + f"DATEPART({fmt}, date_and_time)", + "DATEPART(MILLISECOND, date_and_time)", + ) + for fmt in ("MICROSECOND", "mcs"): + self.validate_identity( + f"DATEPART({fmt}, date_and_time)", + "DATEPART(MICROSECOND, date_and_time)", + ) + for fmt in ("NANOSECOND", "ns"): + self.validate_identity( + f"DATEPART({fmt}, date_and_time)", + "DATEPART(NANOSECOND, date_and_time)", + ) + for fmt in ("WEEKDAY", "dw"): + self.validate_identity( + f"DATEPART({fmt}, date_and_time)", + "DATEPART(WEEKDAY, date_and_time)", + ) + for fmt in ("TZOFFSET", "tz"): + self.validate_identity( + f"DATEPART({fmt}, date_and_time)", + "DATEPART(TZOFFSET, date_and_time)", + ) + for fmt in ("MONTH", "mm", "m"): + self.validate_identity( + f"DATEPART({fmt}, date_and_time)", + "DATEPART(MONTH, date_and_time)", + ) + for fmt in ("DAYOFYEAR", "dy", "y"): + self.validate_identity( + f"DATEPART({fmt}, date_and_time)", + "DATEPART(DAYOFYEAR, date_and_time)", + ) + for fmt in ("DAY", "dd", "d"): + self.validate_identity( + f"DATEPART({fmt}, date_and_time)", + "DATEPART(DAY, date_and_time)", + ) self.validate_all( "SELECT DATEPART(month,'1970-01-01')", write={ - "postgres": "SELECT EXTRACT(month FROM CAST('1970-01-01' AS TIMESTAMP))", - "spark": "SELECT EXTRACT(month FROM CAST('1970-01-01' AS TIMESTAMP))", - "tsql": "SELECT DATEPART(month, CAST('1970-01-01' AS DATETIME2))", + "postgres": "SELECT EXTRACT(month FROM '1970-01-01')", + "spark": "SELECT EXTRACT(month FROM '1970-01-01')", + "tsql": "SELECT DATEPART(month, '1970-01-01')", }, ) self.validate_all( @@ -1529,9 +1570,9 @@ def test_datepart(self): "postgres": "SELECT DATE_PART('YEAR', '2017-01-01'::DATE)", }, write={ - "postgres": "SELECT EXTRACT(YEAR FROM CAST(CAST('2017-01-01' AS DATE) AS TIMESTAMP))", - "spark": "SELECT EXTRACT(YEAR FROM CAST(CAST('2017-01-01' AS DATE) AS TIMESTAMP))", - "tsql": "SELECT DATEPART(YEAR, CAST(CAST('2017-01-01' AS DATE) AS DATETIME2))", + "postgres": "SELECT EXTRACT(YEAR FROM CAST('2017-01-01' AS DATE))", + "spark": "SELECT EXTRACT(YEAR FROM CAST('2017-01-01' AS DATE))", + "tsql": "SELECT DATEPART(YEAR, CAST('2017-01-01' AS DATE))", }, ) self.validate_all( @@ -1540,9 +1581,9 @@ def test_datepart(self): "postgres": "SELECT DATE_PART('month', '2017-03-01'::DATE)", }, write={ - "postgres": "SELECT EXTRACT(month FROM CAST(CAST('2017-03-01' AS DATE) AS TIMESTAMP))", - "spark": "SELECT EXTRACT(month FROM CAST(CAST('2017-03-01' AS DATE) AS TIMESTAMP))", - "tsql": "SELECT DATEPART(month, CAST(CAST('2017-03-01' AS DATE) AS DATETIME2))", + "postgres": "SELECT EXTRACT(month FROM CAST('2017-03-01' AS DATE))", + "spark": "SELECT EXTRACT(month FROM CAST('2017-03-01' AS DATE))", + "tsql": "SELECT DATEPART(month, CAST('2017-03-01' AS DATE))", }, ) self.validate_all( @@ -1551,22 +1592,22 @@ def test_datepart(self): "postgres": "SELECT DATE_PART('day', '2017-01-02'::DATE)", }, write={ - "postgres": "SELECT EXTRACT(day FROM CAST(CAST('2017-01-02' AS DATE) AS TIMESTAMP))", - "spark": "SELECT EXTRACT(day FROM CAST(CAST('2017-01-02' AS DATE) AS TIMESTAMP))", - "tsql": "SELECT DATEPART(day, CAST(CAST('2017-01-02' AS DATE) AS DATETIME2))", + "postgres": "SELECT EXTRACT(day FROM CAST('2017-01-02' AS DATE))", + "spark": "SELECT EXTRACT(day FROM CAST('2017-01-02' AS DATE))", + "tsql": "SELECT DATEPART(day, CAST('2017-01-02' AS DATE))", }, ) for fmt in ("WEEK", "WW", "WK"): self.validate_identity( f"SELECT DATEPART({fmt}, '2024-11-21')", - "SELECT DATEPART(WEEK, CAST('2024-11-21' AS DATETIME2))", + "SELECT DATEPART(WEEK, '2024-11-21')", ) for fmt in ("ISOWK", "ISOWW", "ISO_WEEK"): self.validate_identity( f"SELECT DATEPART({fmt}, '2024-11-21')", - "SELECT DATEPART(ISO_WEEK, CAST('2024-11-21' AS DATETIME2))", + "SELECT DATEPART(ISO_WEEK, '2024-11-21')", ) def test_convert(self): From 712f2a8ba88435142967aaf942bafcc105151ebe Mon Sep 17 00:00:00 2001 From: Lulzim Bilali Date: Mon, 17 Nov 2025 15:33:01 +0100 Subject: [PATCH 7/9] add back casting to datetime2 --- sqlglot/dialects/tsql.py | 7 ++++- tests/dialects/test_tsql.py | 56 ++++++++++++++++++------------------- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/sqlglot/dialects/tsql.py b/sqlglot/dialects/tsql.py index 081cb05af8..408af22458 100644 --- a/sqlglot/dialects/tsql.py +++ b/sqlglot/dialects/tsql.py @@ -706,8 +706,13 @@ def _parse_datepart(self) -> exp.Extract: this = self._parse_var() expression = self._match(TokenType.COMMA) and self._parse_bitwise() name = map_date_part(this, self.dialect) + type_name = "TIMESTAMP" - return self.expression(exp.Extract, this=name, expression=expression) + if isinstance(name, exp.Expression) and name.name == "TIMEZONE_MINUTE": + type_name = "TIMESTAMPTZ" + + expr = self.expression(exp.Cast, this=expression, to=exp.DataType.build(type_name)) + return self.expression(exp.Extract, this=name, expression=expr) def _parse_alter_table_set(self) -> exp.AlterSet: return self._parse_wrapped(super()._parse_alter_table_set) diff --git a/tests/dialects/test_tsql.py b/tests/dialects/test_tsql.py index 86c7c71e62..057784a9a9 100644 --- a/tests/dialects/test_tsql.py +++ b/tests/dialects/test_tsql.py @@ -1494,74 +1494,74 @@ def test_datepart(self): for fmt in ("QUARTER", "qq", "q"): self.validate_identity( f"DATEPART({fmt}, x)", - "DATEPART(QUARTER, x)", + "DATEPART(QUARTER, CAST(x AS DATETIME2))", ) for fmt in ("YEAR", "yy", "yyyy"): self.validate_identity( f"DATEPART({fmt}, x)", - "DATEPART(YEAR, x)", + "DATEPART(YEAR, CAST(x AS DATETIME2))", ) for fmt in ("HOUR", "hh"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(HOUR, date_and_time)", + "DATEPART(HOUR, CAST(date_and_time AS DATETIME2))", ) for fmt in ("MINUTE", "mi", "n"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(MINUTE, date_and_time)", + "DATEPART(MINUTE, CAST(date_and_time AS DATETIME2))", ) for fmt in ("SECOND", "ss", "s"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(SECOND, date_and_time)", + "DATEPART(SECOND, CAST(date_and_time AS DATETIME2))", ) for fmt in ("MILLISECOND", "ms"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(MILLISECOND, date_and_time)", + "DATEPART(MILLISECOND, CAST(date_and_time AS DATETIME2))", ) for fmt in ("MICROSECOND", "mcs"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(MICROSECOND, date_and_time)", + "DATEPART(MICROSECOND, CAST(date_and_time AS DATETIME2))", ) for fmt in ("NANOSECOND", "ns"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(NANOSECOND, date_and_time)", + "DATEPART(NANOSECOND, CAST(date_and_time AS DATETIME2))", ) for fmt in ("WEEKDAY", "dw"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(WEEKDAY, date_and_time)", + "DATEPART(WEEKDAY, CAST(date_and_time AS DATETIME2))", ) for fmt in ("TZOFFSET", "tz"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(TZOFFSET, date_and_time)", + "DATEPART(TZOFFSET, CAST(date_and_time AS DATETIMEOFFSET))", ) for fmt in ("MONTH", "mm", "m"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(MONTH, date_and_time)", + "DATEPART(MONTH, CAST(date_and_time AS DATETIME2))", ) for fmt in ("DAYOFYEAR", "dy", "y"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(DAYOFYEAR, date_and_time)", + "DATEPART(DAYOFYEAR, CAST(date_and_time AS DATETIME2))", ) for fmt in ("DAY", "dd", "d"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(DAY, date_and_time)", + "DATEPART(DAY, CAST(date_and_time AS DATETIME2))", ) self.validate_all( - "SELECT DATEPART(month,'1970-01-01')", + "SELECT DATEPART(month, '1970-01-01')", write={ - "postgres": "SELECT EXTRACT(month FROM '1970-01-01')", - "spark": "SELECT EXTRACT(month FROM '1970-01-01')", - "tsql": "SELECT DATEPART(month, '1970-01-01')", + "postgres": "SELECT EXTRACT(month FROM CAST('1970-01-01' AS TIMESTAMP))", + "spark": "SELECT EXTRACT(month FROM CAST('1970-01-01' AS TIMESTAMP))", + "tsql": "SELECT DATEPART(month, CAST('1970-01-01' AS DATETIME2))", }, ) self.validate_all( @@ -1570,9 +1570,9 @@ def test_datepart(self): "postgres": "SELECT DATE_PART('YEAR', '2017-01-01'::DATE)", }, write={ - "postgres": "SELECT EXTRACT(YEAR FROM CAST('2017-01-01' AS DATE))", - "spark": "SELECT EXTRACT(YEAR FROM CAST('2017-01-01' AS DATE))", - "tsql": "SELECT DATEPART(YEAR, CAST('2017-01-01' AS DATE))", + "postgres": "SELECT EXTRACT(YEAR FROM CAST(CAST('2017-01-01' AS DATE) AS TIMESTAMP))", + "spark": "SELECT EXTRACT(YEAR FROM CAST(CAST('2017-01-01' AS DATE) AS TIMESTAMP))", + "tsql": "SELECT DATEPART(YEAR, CAST(CAST('2017-01-01' AS DATE) AS DATETIME2))", }, ) self.validate_all( @@ -1581,9 +1581,9 @@ def test_datepart(self): "postgres": "SELECT DATE_PART('month', '2017-03-01'::DATE)", }, write={ - "postgres": "SELECT EXTRACT(month FROM CAST('2017-03-01' AS DATE))", - "spark": "SELECT EXTRACT(month FROM CAST('2017-03-01' AS DATE))", - "tsql": "SELECT DATEPART(month, CAST('2017-03-01' AS DATE))", + "postgres": "SELECT EXTRACT(month FROM CAST(CAST('2017-03-01' AS DATE) AS TIMESTAMP))", + "spark": "SELECT EXTRACT(month FROM CAST(CAST('2017-03-01' AS DATE) AS TIMESTAMP))", + "tsql": "SELECT DATEPART(month, CAST(CAST('2017-03-01' AS DATE) AS DATETIME2))", }, ) self.validate_all( @@ -1592,22 +1592,22 @@ def test_datepart(self): "postgres": "SELECT DATE_PART('day', '2017-01-02'::DATE)", }, write={ - "postgres": "SELECT EXTRACT(day FROM CAST('2017-01-02' AS DATE))", - "spark": "SELECT EXTRACT(day FROM CAST('2017-01-02' AS DATE))", - "tsql": "SELECT DATEPART(day, CAST('2017-01-02' AS DATE))", + "postgres": "SELECT EXTRACT(day FROM CAST(CAST('2017-01-02' AS DATE) AS TIMESTAMP))", + "spark": "SELECT EXTRACT(day FROM CAST(CAST('2017-01-02' AS DATE) AS TIMESTAMP))", + "tsql": "SELECT DATEPART(day, CAST(CAST('2017-01-02' AS DATE) AS DATETIME2))", }, ) for fmt in ("WEEK", "WW", "WK"): self.validate_identity( f"SELECT DATEPART({fmt}, '2024-11-21')", - "SELECT DATEPART(WEEK, '2024-11-21')", + "SELECT DATEPART(WEEK, CAST('2024-11-21' AS DATETIME2))", ) for fmt in ("ISOWK", "ISOWW", "ISO_WEEK"): self.validate_identity( f"SELECT DATEPART({fmt}, '2024-11-21')", - "SELECT DATEPART(ISO_WEEK, '2024-11-21')", + "SELECT DATEPART(ISO_WEEK, CAST('2024-11-21' AS DATETIME2))", ) def test_convert(self): From b8cd9cdb0ceacce96e2ada10c20a9970c52e0cc5 Mon Sep 17 00:00:00 2001 From: Lulzim Bilali Date: Tue, 18 Nov 2025 09:21:03 +0100 Subject: [PATCH 8/9] Revert "add back casting to datetime2" This reverts commit 712f2a8ba88435142967aaf942bafcc105151ebe. --- sqlglot/dialects/tsql.py | 7 +---- tests/dialects/test_tsql.py | 56 ++++++++++++++++++------------------- 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/sqlglot/dialects/tsql.py b/sqlglot/dialects/tsql.py index 408af22458..081cb05af8 100644 --- a/sqlglot/dialects/tsql.py +++ b/sqlglot/dialects/tsql.py @@ -706,13 +706,8 @@ def _parse_datepart(self) -> exp.Extract: this = self._parse_var() expression = self._match(TokenType.COMMA) and self._parse_bitwise() name = map_date_part(this, self.dialect) - type_name = "TIMESTAMP" - if isinstance(name, exp.Expression) and name.name == "TIMEZONE_MINUTE": - type_name = "TIMESTAMPTZ" - - expr = self.expression(exp.Cast, this=expression, to=exp.DataType.build(type_name)) - return self.expression(exp.Extract, this=name, expression=expr) + return self.expression(exp.Extract, this=name, expression=expression) def _parse_alter_table_set(self) -> exp.AlterSet: return self._parse_wrapped(super()._parse_alter_table_set) diff --git a/tests/dialects/test_tsql.py b/tests/dialects/test_tsql.py index 057784a9a9..86c7c71e62 100644 --- a/tests/dialects/test_tsql.py +++ b/tests/dialects/test_tsql.py @@ -1494,74 +1494,74 @@ def test_datepart(self): for fmt in ("QUARTER", "qq", "q"): self.validate_identity( f"DATEPART({fmt}, x)", - "DATEPART(QUARTER, CAST(x AS DATETIME2))", + "DATEPART(QUARTER, x)", ) for fmt in ("YEAR", "yy", "yyyy"): self.validate_identity( f"DATEPART({fmt}, x)", - "DATEPART(YEAR, CAST(x AS DATETIME2))", + "DATEPART(YEAR, x)", ) for fmt in ("HOUR", "hh"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(HOUR, CAST(date_and_time AS DATETIME2))", + "DATEPART(HOUR, date_and_time)", ) for fmt in ("MINUTE", "mi", "n"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(MINUTE, CAST(date_and_time AS DATETIME2))", + "DATEPART(MINUTE, date_and_time)", ) for fmt in ("SECOND", "ss", "s"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(SECOND, CAST(date_and_time AS DATETIME2))", + "DATEPART(SECOND, date_and_time)", ) for fmt in ("MILLISECOND", "ms"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(MILLISECOND, CAST(date_and_time AS DATETIME2))", + "DATEPART(MILLISECOND, date_and_time)", ) for fmt in ("MICROSECOND", "mcs"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(MICROSECOND, CAST(date_and_time AS DATETIME2))", + "DATEPART(MICROSECOND, date_and_time)", ) for fmt in ("NANOSECOND", "ns"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(NANOSECOND, CAST(date_and_time AS DATETIME2))", + "DATEPART(NANOSECOND, date_and_time)", ) for fmt in ("WEEKDAY", "dw"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(WEEKDAY, CAST(date_and_time AS DATETIME2))", + "DATEPART(WEEKDAY, date_and_time)", ) for fmt in ("TZOFFSET", "tz"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(TZOFFSET, CAST(date_and_time AS DATETIMEOFFSET))", + "DATEPART(TZOFFSET, date_and_time)", ) for fmt in ("MONTH", "mm", "m"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(MONTH, CAST(date_and_time AS DATETIME2))", + "DATEPART(MONTH, date_and_time)", ) for fmt in ("DAYOFYEAR", "dy", "y"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(DAYOFYEAR, CAST(date_and_time AS DATETIME2))", + "DATEPART(DAYOFYEAR, date_and_time)", ) for fmt in ("DAY", "dd", "d"): self.validate_identity( f"DATEPART({fmt}, date_and_time)", - "DATEPART(DAY, CAST(date_and_time AS DATETIME2))", + "DATEPART(DAY, date_and_time)", ) self.validate_all( - "SELECT DATEPART(month, '1970-01-01')", + "SELECT DATEPART(month,'1970-01-01')", write={ - "postgres": "SELECT EXTRACT(month FROM CAST('1970-01-01' AS TIMESTAMP))", - "spark": "SELECT EXTRACT(month FROM CAST('1970-01-01' AS TIMESTAMP))", - "tsql": "SELECT DATEPART(month, CAST('1970-01-01' AS DATETIME2))", + "postgres": "SELECT EXTRACT(month FROM '1970-01-01')", + "spark": "SELECT EXTRACT(month FROM '1970-01-01')", + "tsql": "SELECT DATEPART(month, '1970-01-01')", }, ) self.validate_all( @@ -1570,9 +1570,9 @@ def test_datepart(self): "postgres": "SELECT DATE_PART('YEAR', '2017-01-01'::DATE)", }, write={ - "postgres": "SELECT EXTRACT(YEAR FROM CAST(CAST('2017-01-01' AS DATE) AS TIMESTAMP))", - "spark": "SELECT EXTRACT(YEAR FROM CAST(CAST('2017-01-01' AS DATE) AS TIMESTAMP))", - "tsql": "SELECT DATEPART(YEAR, CAST(CAST('2017-01-01' AS DATE) AS DATETIME2))", + "postgres": "SELECT EXTRACT(YEAR FROM CAST('2017-01-01' AS DATE))", + "spark": "SELECT EXTRACT(YEAR FROM CAST('2017-01-01' AS DATE))", + "tsql": "SELECT DATEPART(YEAR, CAST('2017-01-01' AS DATE))", }, ) self.validate_all( @@ -1581,9 +1581,9 @@ def test_datepart(self): "postgres": "SELECT DATE_PART('month', '2017-03-01'::DATE)", }, write={ - "postgres": "SELECT EXTRACT(month FROM CAST(CAST('2017-03-01' AS DATE) AS TIMESTAMP))", - "spark": "SELECT EXTRACT(month FROM CAST(CAST('2017-03-01' AS DATE) AS TIMESTAMP))", - "tsql": "SELECT DATEPART(month, CAST(CAST('2017-03-01' AS DATE) AS DATETIME2))", + "postgres": "SELECT EXTRACT(month FROM CAST('2017-03-01' AS DATE))", + "spark": "SELECT EXTRACT(month FROM CAST('2017-03-01' AS DATE))", + "tsql": "SELECT DATEPART(month, CAST('2017-03-01' AS DATE))", }, ) self.validate_all( @@ -1592,22 +1592,22 @@ def test_datepart(self): "postgres": "SELECT DATE_PART('day', '2017-01-02'::DATE)", }, write={ - "postgres": "SELECT EXTRACT(day FROM CAST(CAST('2017-01-02' AS DATE) AS TIMESTAMP))", - "spark": "SELECT EXTRACT(day FROM CAST(CAST('2017-01-02' AS DATE) AS TIMESTAMP))", - "tsql": "SELECT DATEPART(day, CAST(CAST('2017-01-02' AS DATE) AS DATETIME2))", + "postgres": "SELECT EXTRACT(day FROM CAST('2017-01-02' AS DATE))", + "spark": "SELECT EXTRACT(day FROM CAST('2017-01-02' AS DATE))", + "tsql": "SELECT DATEPART(day, CAST('2017-01-02' AS DATE))", }, ) for fmt in ("WEEK", "WW", "WK"): self.validate_identity( f"SELECT DATEPART({fmt}, '2024-11-21')", - "SELECT DATEPART(WEEK, CAST('2024-11-21' AS DATETIME2))", + "SELECT DATEPART(WEEK, '2024-11-21')", ) for fmt in ("ISOWK", "ISOWW", "ISO_WEEK"): self.validate_identity( f"SELECT DATEPART({fmt}, '2024-11-21')", - "SELECT DATEPART(ISO_WEEK, CAST('2024-11-21' AS DATETIME2))", + "SELECT DATEPART(ISO_WEEK, '2024-11-21')", ) def test_convert(self): From 2d12b96364464e4af4f2fac9715222ea13750c4e Mon Sep 17 00:00:00 2001 From: Jo <46752250+georgesittas@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:33:52 +0200 Subject: [PATCH 9/9] Remove invalid test --- tests/dialects/test_tsql.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/dialects/test_tsql.py b/tests/dialects/test_tsql.py index 86c7c71e62..e2dc0c6754 100644 --- a/tests/dialects/test_tsql.py +++ b/tests/dialects/test_tsql.py @@ -1559,7 +1559,6 @@ def test_datepart(self): self.validate_all( "SELECT DATEPART(month,'1970-01-01')", write={ - "postgres": "SELECT EXTRACT(month FROM '1970-01-01')", "spark": "SELECT EXTRACT(month FROM '1970-01-01')", "tsql": "SELECT DATEPART(month, '1970-01-01')", },