Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions sqlglot/dialects/dialect.py
Original file line number Diff line number Diff line change
Expand Up @@ -2092,8 +2092,7 @@ def unit_to_var(expression: exp.Expr, default: str = "DAY") -> exp.Expr | None:

def week_unit_to_dow(unit: exp.Expr | None) -> int | None:
"""
Compute the week start day for a week-ish diff unit, e.g BigQuery's WEEK(<day>)
or ISOWEEK unit parts.
Compute the week start day for a week-ish diff unit, e.g BigQuery's WEEK(<day>) or ISOWEEK unit parts.

Args:
unit: The unit expression (Var for WEEK/ISOWEEK or WeekStart)
Expand Down
4 changes: 2 additions & 2 deletions sqlglot/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -5179,8 +5179,8 @@ def lastday_sql(self, expression: exp.LastDay) -> str:
if self.LAST_DAY_SUPPORTS_DATE_PART:
return self.function_fallback_sql(expression)

unit = expression.text("unit")
if unit and unit != "MONTH":
unit = expression.args.get("unit")
if unit and unit.name.upper() != "MONTH":
self.unsupported("Date parts are not supported in LAST_DAY.")

return self.func("LAST_DAY", expression.this)
Expand Down
32 changes: 18 additions & 14 deletions sqlglot/generators/duckdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,24 @@ def _last_day_sql(self: DuckDBGenerator, expression: exp.LastDay) -> str:
For other date parts (year, quarter, week), we need to implement equivalent logic.
"""
date_expr = expression.this
unit_expr = expression.args.get("unit")

week_start = week_unit_to_dow(unit_expr)
if week_start:
Comment thread
georgesittas marked this conversation as resolved.
# The week's last day precedes its start day; DuckDB DAYOFWEEK: Sunday=0, ..., Saturday=6
last_dow = week_start - 1
dow = exp.func("EXTRACT", "DAYOFWEEK", date_expr)

# Days to the last day of week: (last_dow + 7 - dayofweek) % 7
days_to_last_expr = exp.Mod(
this=exp.Paren(this=exp.Sub(this=exp.Literal.number(last_dow + 7), expression=dow)),
expression=exp.Literal.number(7),
)
interval_expr = exp.Interval(this=days_to_last_expr, unit=exp.var("DAY"))
add_expr = exp.Add(this=date_expr, expression=interval_expr)

return self.sql(exp.cast(add_expr, exp.DType.DATE))

unit = expression.text("unit")

if not unit or unit.upper() == "MONTH":
Expand Down Expand Up @@ -189,20 +207,6 @@ def _last_day_sql(self: DuckDBGenerator, expression: exp.LastDay) -> str:
last_day_expr = exp.func("LAST_DAY", first_day_last_month_expr)
return self.sql(last_day_expr)

if unit.upper() == "WEEK":
# DuckDB DAYOFWEEK: Sunday=0, Monday=1, ..., Saturday=6
dow = exp.func("EXTRACT", "DAYOFWEEK", date_expr)
# Days to the last day of week: (7 - dayofweek) % 7, assuming the last day of week is Sunday (Snowflake)
# Wrap in parentheses to ensure correct precedence
days_to_sunday_expr = exp.Mod(
this=exp.Paren(this=exp.Sub(this=exp.Literal.number(7), expression=dow)),
expression=exp.Literal.number(7),
)
interval_expr = exp.Interval(this=days_to_sunday_expr, unit=exp.var("DAY"))
add_expr = exp.Add(this=date_expr, expression=interval_expr)
cast_expr = exp.cast(add_expr, exp.DType.DATE)
return self.sql(cast_expr)

self.unsupported(f"Unsupported date part '{unit}' in LAST_DAY function")
return self.function_fallback_sql(expression)

Expand Down
28 changes: 17 additions & 11 deletions sqlglot/parsers/bigquery.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,28 @@ def _build_date(args: list) -> exp.Date | exp.DateFromParts:
return expr_type.from_arg_list(args)


def _normalize_bare_week(expr: E) -> E:
# In BigQuery, a bare WEEK date part is equivalent to WEEK(SUNDAY)
unit = expr.args.get("unit")
if isinstance(unit, exp.Var) and unit.name.upper() == "WEEK":
expr.set("unit", exp.WeekStart(this=exp.var("SUNDAY")))

return expr


def build_date_diff(
expr_type: type[exp.DateDiff | exp.DatetimeDiff],
) -> t.Callable[[list], exp.Expr]:
def _builder(args: list) -> exp.Expr:
expr = expr_type(
this=seq_get(args, 0),
expression=seq_get(args, 1),
unit=seq_get(args, 2),
date_part_boundary=True,
return _normalize_bare_week(
expr_type(
this=seq_get(args, 0),
expression=seq_get(args, 1),
unit=seq_get(args, 2),
date_part_boundary=True,
)
)

unit = expr.args.get("unit")
if isinstance(unit, exp.Var) and unit.name.upper() == "WEEK":
expr.set("unit", exp.WeekStart(this=exp.var("SUNDAY")))

return expr

return _builder


Expand Down Expand Up @@ -253,6 +258,7 @@ class BigQueryParser(parser.Parser):
"JSON_STRIP_NULLS": _build_json_strip_nulls,
"JSON_VALUE": _build_extract_json_with_default_path(exp.JSONExtractScalar),
"JSON_VALUE_ARRAY": _build_extract_json_with_default_path(exp.JSONValueArray),
"LAST_DAY": lambda args: _normalize_bare_week(exp.LastDay.from_arg_list(args)),
"LENGTH": lambda args: exp.Length(this=seq_get(args, 0), binary=True),
"MD5": exp.MD5Digest.from_arg_list,
"SHA1": exp.SHA1Digest.from_arg_list,
Expand Down
29 changes: 29 additions & 0 deletions tests/dialects/test_bigquery.py
Original file line number Diff line number Diff line change
Expand Up @@ -3569,6 +3569,35 @@ def test_week(self):
)
self.validate_identity("DATE_DIFF('2017-12-18', '2017-12-17', WEEK(SATURDAY))")
self.validate_identity("DATETIME_DIFF('2017-12-18', '2017-12-17', WEEK(MONDAY))")

self.validate_all(
"SELECT LAST_DAY(DATE '2008-11-10', WEEK(SUNDAY))",
write={
"bigquery": "SELECT LAST_DAY(CAST('2008-11-10' AS DATE), WEEK)",
"duckdb": "SELECT CAST(CAST('2008-11-10' AS DATE) + INTERVAL ((13 - EXTRACT(DAYOFWEEK FROM CAST('2008-11-10' AS DATE))) % 7) DAY AS DATE)",
},
)
self.validate_all(
"SELECT LAST_DAY(DATE '2008-11-10', WEEK)",
write={
"bigquery": "SELECT LAST_DAY(CAST('2008-11-10' AS DATE), WEEK)",
"duckdb": "SELECT CAST(CAST('2008-11-10' AS DATE) + INTERVAL ((13 - EXTRACT(DAYOFWEEK FROM CAST('2008-11-10' AS DATE))) % 7) DAY AS DATE)",
},
)
self.validate_all(
"SELECT LAST_DAY(DATE '2008-11-10', WEEK(MONDAY))",
write={
"bigquery": "SELECT LAST_DAY(CAST('2008-11-10' AS DATE), WEEK(MONDAY))",
"duckdb": "SELECT CAST(CAST('2008-11-10' AS DATE) + INTERVAL ((7 - EXTRACT(DAYOFWEEK FROM CAST('2008-11-10' AS DATE))) % 7) DAY AS DATE)",
},
)
self.validate_all(
"SELECT LAST_DAY(DATE '2008-11-10', ISOWEEK)",
write={
"bigquery": "SELECT LAST_DAY(CAST('2008-11-10' AS DATE), ISOWEEK)",
"duckdb": "SELECT CAST(CAST('2008-11-10' AS DATE) + INTERVAL ((7 - EXTRACT(DAYOFWEEK FROM CAST('2008-11-10' AS DATE))) % 7) DAY AS DATE)",
},
)
self.validate_identity(
"EXTRACT(WEEK(THURSDAY) FROM DATE '2013-12-25')",
"EXTRACT(WEEK(THURSDAY) FROM CAST('2013-12-25' AS DATE))",
Expand Down
Loading