diff options
Diffstat (limited to 'sqlglot/dialects')
-rw-r--r-- | sqlglot/dialects/bigquery.py | 36 | ||||
-rw-r--r-- | sqlglot/dialects/clickhouse.py | 17 | ||||
-rw-r--r-- | sqlglot/dialects/dialect.py | 1 | ||||
-rw-r--r-- | sqlglot/dialects/drill.py | 1 | ||||
-rw-r--r-- | sqlglot/dialects/duckdb.py | 21 | ||||
-rw-r--r-- | sqlglot/dialects/hive.py | 15 | ||||
-rw-r--r-- | sqlglot/dialects/mysql.py | 29 | ||||
-rw-r--r-- | sqlglot/dialects/oracle.py | 1 | ||||
-rw-r--r-- | sqlglot/dialects/postgres.py | 26 | ||||
-rw-r--r-- | sqlglot/dialects/presto.py | 1 | ||||
-rw-r--r-- | sqlglot/dialects/redshift.py | 1 | ||||
-rw-r--r-- | sqlglot/dialects/snowflake.py | 1 | ||||
-rw-r--r-- | sqlglot/dialects/spark2.py | 3 | ||||
-rw-r--r-- | sqlglot/dialects/sqlite.py | 1 | ||||
-rw-r--r-- | sqlglot/dialects/tableau.py | 1 | ||||
-rw-r--r-- | sqlglot/dialects/teradata.py | 3 | ||||
-rw-r--r-- | sqlglot/dialects/tsql.py | 7 |
17 files changed, 129 insertions, 36 deletions
diff --git a/sqlglot/dialects/bigquery.py b/sqlglot/dialects/bigquery.py index c9d6c79..82162b4 100644 --- a/sqlglot/dialects/bigquery.py +++ b/sqlglot/dialects/bigquery.py @@ -163,6 +163,17 @@ def _pushdown_cte_column_names(expression: exp.Expression) -> exp.Expression: return expression +def _parse_timestamp(args: t.List) -> exp.StrToTime: + this = format_time_lambda(exp.StrToTime, "bigquery")([seq_get(args, 1), seq_get(args, 0)]) + this.set("zone", seq_get(args, 2)) + return this + + +def _parse_date(args: t.List) -> exp.Date | exp.DateFromParts: + expr_type = exp.DateFromParts if len(args) == 3 else exp.Date + return expr_type.from_arg_list(args) + + class BigQuery(Dialect): UNNEST_COLUMN_ONLY = True @@ -203,8 +214,10 @@ class BigQuery(Dialect): while isinstance(parent, exp.Dot): parent = parent.parent - if not (isinstance(parent, exp.Table) and parent.db) and not expression.meta.get( - "is_table" + if ( + not isinstance(parent, exp.UserDefinedFunction) + and not (isinstance(parent, exp.Table) and parent.db) + and not expression.meta.get("is_table") ): expression.set("this", expression.this.lower()) @@ -251,6 +264,7 @@ class BigQuery(Dialect): FUNCTIONS = { **parser.Parser.FUNCTIONS, + "DATE": _parse_date, "DATE_ADD": parse_date_delta_with_interval(exp.DateAdd), "DATE_SUB": parse_date_delta_with_interval(exp.DateSub), "DATE_TRUNC": lambda args: exp.DateTrunc( @@ -264,9 +278,7 @@ class BigQuery(Dialect): "PARSE_DATE": lambda args: format_time_lambda(exp.StrToDate, "bigquery")( [seq_get(args, 1), seq_get(args, 0)] ), - "PARSE_TIMESTAMP": lambda args: format_time_lambda(exp.StrToTime, "bigquery")( - [seq_get(args, 1), seq_get(args, 0)] - ), + "PARSE_TIMESTAMP": _parse_timestamp, "REGEXP_CONTAINS": exp.RegexpLike.from_arg_list, "REGEXP_EXTRACT": lambda args: exp.RegexpExtract( this=seq_get(args, 0), @@ -356,9 +368,11 @@ class BigQuery(Dialect): EXPLICIT_UNION = True INTERVAL_ALLOWS_PLURAL_FORM = False JOIN_HINTS = False + QUERY_HINTS = False TABLE_HINTS = False LIMIT_FETCH = "LIMIT" RENAME_TABLE_WITH_DB = False + ESCAPE_LINE_BREAK = True TRANSFORMS = { **generator.Generator.TRANSFORMS, @@ -367,6 +381,7 @@ class BigQuery(Dialect): exp.Cast: transforms.preprocess([transforms.remove_precision_parameterized_types]), exp.CTE: transforms.preprocess([_pushdown_cte_column_names]), exp.DateAdd: _date_add_sql("DATE", "ADD"), + exp.DateFromParts: rename_func("DATE"), exp.DateSub: _date_add_sql("DATE", "SUB"), exp.DatetimeAdd: _date_add_sql("DATETIME", "ADD"), exp.DatetimeSub: _date_add_sql("DATETIME", "SUB"), @@ -397,7 +412,9 @@ class BigQuery(Dialect): ] ), exp.StrToDate: lambda self, e: f"PARSE_DATE({self.format_time(e)}, {self.sql(e, 'this')})", - exp.StrToTime: lambda self, e: f"PARSE_TIMESTAMP({self.format_time(e)}, {self.sql(e, 'this')})", + exp.StrToTime: lambda self, e: self.func( + "PARSE_TIMESTAMP", self.format_time(e), e.this, e.args.get("zone") + ), exp.TimeAdd: _date_add_sql("TIME", "ADD"), exp.TimeSub: _date_add_sql("TIME", "SUB"), exp.TimestampAdd: _date_add_sql("TIMESTAMP", "ADD"), @@ -548,10 +565,15 @@ class BigQuery(Dialect): } def attimezone_sql(self, expression: exp.AtTimeZone) -> str: - if not isinstance(expression.parent, exp.Cast): + parent = expression.parent + + # BigQuery allows CAST(.. AS {STRING|TIMESTAMP} [FORMAT <fmt> [AT TIME ZONE <tz>]]). + # Only the TIMESTAMP one should use the below conversion, when AT TIME ZONE is included. + if not isinstance(parent, exp.Cast) or not parent.to.is_type("text"): return self.func( "TIMESTAMP", self.func("DATETIME", expression.this, expression.args.get("zone")) ) + return super().attimezone_sql(expression) def trycast_sql(self, expression: exp.TryCast) -> str: diff --git a/sqlglot/dialects/clickhouse.py b/sqlglot/dialects/clickhouse.py index efaf34c..9126c4b 100644 --- a/sqlglot/dialects/clickhouse.py +++ b/sqlglot/dialects/clickhouse.py @@ -109,10 +109,11 @@ class ClickHouse(Dialect): QUERY_MODIFIER_PARSERS = { **parser.Parser.QUERY_MODIFIER_PARSERS, - "settings": lambda self: self._parse_csv(self._parse_conjunction) - if self._match(TokenType.SETTINGS) - else None, - "format": lambda self: self._parse_id_var() if self._match(TokenType.FORMAT) else None, + TokenType.SETTINGS: lambda self: ( + "settings", + self._advance() or self._parse_csv(self._parse_conjunction), + ), + TokenType.FORMAT: lambda self: ("format", self._advance() or self._parse_id_var()), } def _parse_conjunction(self) -> t.Optional[exp.Expression]: @@ -155,9 +156,12 @@ class ClickHouse(Dialect): return this def _parse_table( - self, schema: bool = False, alias_tokens: t.Optional[t.Collection[TokenType]] = None + self, + schema: bool = False, + joins: bool = False, + alias_tokens: t.Optional[t.Collection[TokenType]] = None, ) -> t.Optional[exp.Expression]: - this = super()._parse_table(schema=schema, alias_tokens=alias_tokens) + this = super()._parse_table(schema=schema, joins=joins, alias_tokens=alias_tokens) if self._match(TokenType.FINAL): this = self.expression(exp.Final, this=this) @@ -273,6 +277,7 @@ class ClickHouse(Dialect): return None class Generator(generator.Generator): + QUERY_HINTS = False STRUCT_DELIMITER = ("(", ")") TYPE_MAPPING = { diff --git a/sqlglot/dialects/dialect.py b/sqlglot/dialects/dialect.py index d258826..4fc93bf 100644 --- a/sqlglot/dialects/dialect.py +++ b/sqlglot/dialects/dialect.py @@ -98,7 +98,6 @@ class _Dialect(type): klass.BIT_START, klass.BIT_END = get_start_end(TokenType.BIT_STRING) klass.HEX_START, klass.HEX_END = get_start_end(TokenType.HEX_STRING) klass.BYTE_START, klass.BYTE_END = get_start_end(TokenType.BYTE_STRING) - klass.RAW_START, klass.RAW_END = get_start_end(TokenType.RAW_STRING) dialect_properties = { **{ diff --git a/sqlglot/dialects/drill.py b/sqlglot/dialects/drill.py index 3cca986..26d09ce 100644 --- a/sqlglot/dialects/drill.py +++ b/sqlglot/dialects/drill.py @@ -96,6 +96,7 @@ class Drill(Dialect): class Generator(generator.Generator): JOIN_HINTS = False TABLE_HINTS = False + QUERY_HINTS = False TYPE_MAPPING = { **generator.Generator.TYPE_MAPPING, diff --git a/sqlglot/dialects/duckdb.py b/sqlglot/dialects/duckdb.py index 093a01c..d7e5a43 100644 --- a/sqlglot/dialects/duckdb.py +++ b/sqlglot/dialects/duckdb.py @@ -8,6 +8,7 @@ from sqlglot.dialects.dialect import ( approx_count_distinct_sql, arrow_json_extract_scalar_sql, arrow_json_extract_sql, + date_trunc_to_time, datestrtodate_sql, format_time_lambda, no_comment_column_constraint_sql, @@ -38,6 +39,21 @@ def _date_delta_sql(self: generator.Generator, expression: exp.DateAdd | exp.Dat return f"{this} {op} {self.sql(exp.Interval(this=expression.expression, unit=unit))}" +# BigQuery -> DuckDB conversion for the DATE function +def _date_sql(self: generator.Generator, expression: exp.Date) -> str: + result = f"CAST({self.sql(expression, 'this')} AS DATE)" + zone = self.sql(expression, "zone") + + if zone: + date_str = self.func("STRFTIME", result, "'%d/%m/%Y'") + date_str = f"{date_str} || ' ' || {zone}" + + # This will create a TIMESTAMP with time zone information + result = self.func("STRPTIME", date_str, "'%d/%m/%Y %Z'") + + return result + + def _array_sort_sql(self: generator.Generator, expression: exp.ArraySort) -> str: if expression.expression: self.unsupported("DUCKDB ARRAY_SORT does not support a comparator") @@ -131,6 +147,8 @@ class DuckDB(Dialect): "ARRAY_REVERSE_SORT": _sort_array_reverse, "DATEDIFF": _parse_date_diff, "DATE_DIFF": _parse_date_diff, + "DATE_TRUNC": date_trunc_to_time, + "DATETRUNC": date_trunc_to_time, "EPOCH": exp.TimeToUnix.from_arg_list, "EPOCH_MS": lambda args: exp.UnixToTime( this=exp.Div(this=seq_get(args, 0), expression=exp.Literal.number(1000)) @@ -167,6 +185,7 @@ class DuckDB(Dialect): class Generator(generator.Generator): JOIN_HINTS = False TABLE_HINTS = False + QUERY_HINTS = False LIMIT_FETCH = "LIMIT" STRUCT_DELIMITER = ("(", ")") RENAME_TABLE_WITH_DB = False @@ -188,7 +207,9 @@ class DuckDB(Dialect): exp.DayOfWeek: rename_func("DAYOFWEEK"), exp.DayOfYear: rename_func("DAYOFYEAR"), exp.DataType: _datatype_sql, + exp.Date: _date_sql, exp.DateAdd: _date_delta_sql, + exp.DateFromParts: rename_func("MAKE_DATE"), exp.DateSub: _date_delta_sql, exp.DateDiff: lambda self, e: self.func( "DATE_DIFF", f"'{e.args.get('unit', 'day')}'", e.expression, e.this diff --git a/sqlglot/dialects/hive.py b/sqlglot/dialects/hive.py index 6bca610..1abc0f4 100644 --- a/sqlglot/dialects/hive.py +++ b/sqlglot/dialects/hive.py @@ -273,13 +273,6 @@ class Hive(Dialect): ), } - QUERY_MODIFIER_PARSERS = { - **parser.Parser.QUERY_MODIFIER_PARSERS, - "cluster": lambda self: self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY), - "distribute": lambda self: self._parse_sort(exp.Distribute, TokenType.DISTRIBUTE_BY), - "sort": lambda self: self._parse_sort(exp.Sort, TokenType.SORT_BY), - } - def _parse_types( self, check_func: bool = False, schema: bool = False ) -> t.Optional[exp.Expression]: @@ -319,6 +312,7 @@ class Hive(Dialect): TABLESAMPLE_SIZE_IS_PERCENT = True JOIN_HINTS = False TABLE_HINTS = False + QUERY_HINTS = False INDEX_ON = "ON TABLE" TYPE_MAPPING = { @@ -429,10 +423,3 @@ class Hive(Dialect): expression = exp.DataType.build(expression.this) return super().datatype_sql(expression) - - def after_having_modifiers(self, expression: exp.Expression) -> t.List[str]: - return super().after_having_modifiers(expression) + [ - self.sql(expression, "distribute"), - self.sql(expression, "sort"), - self.sql(expression, "cluster"), - ] diff --git a/sqlglot/dialects/mysql.py b/sqlglot/dialects/mysql.py index 5f743ee..bae0e50 100644 --- a/sqlglot/dialects/mysql.py +++ b/sqlglot/dialects/mysql.py @@ -123,14 +123,15 @@ class MySQL(Dialect): KEYWORDS = { **tokens.Tokenizer.KEYWORDS, "CHARSET": TokenType.CHARACTER_SET, + "ENUM": TokenType.ENUM, "FORCE": TokenType.FORCE, "IGNORE": TokenType.IGNORE, "LONGBLOB": TokenType.LONGBLOB, "LONGTEXT": TokenType.LONGTEXT, "MEDIUMBLOB": TokenType.MEDIUMBLOB, "MEDIUMTEXT": TokenType.MEDIUMTEXT, + "MEMBER OF": TokenType.MEMBER_OF, "SEPARATOR": TokenType.SEPARATOR, - "ENUM": TokenType.ENUM, "START": TokenType.BEGIN, "SIGNED": TokenType.BIGINT, "SIGNED INTEGER": TokenType.BIGINT, @@ -185,11 +186,26 @@ class MySQL(Dialect): COMMANDS = tokens.Tokenizer.COMMANDS - {TokenType.SHOW} class Parser(parser.Parser): - FUNC_TOKENS = {*parser.Parser.FUNC_TOKENS, TokenType.SCHEMA, TokenType.DATABASE} + FUNC_TOKENS = { + *parser.Parser.FUNC_TOKENS, + TokenType.DATABASE, + TokenType.SCHEMA, + TokenType.VALUES, + } + TABLE_ALIAS_TOKENS = ( parser.Parser.TABLE_ALIAS_TOKENS - parser.Parser.TABLE_INDEX_HINT_TOKENS ) + RANGE_PARSERS = { + **parser.Parser.RANGE_PARSERS, + TokenType.MEMBER_OF: lambda self, this: self.expression( + exp.JSONArrayContains, + this=this, + expression=self._parse_wrapped(self._parse_expression), + ), + } + FUNCTIONS = { **parser.Parser.FUNCTIONS, "DATE_ADD": parse_date_delta_with_interval(exp.DateAdd), @@ -207,6 +223,10 @@ class MySQL(Dialect): this=self._parse_lambda(), separator=self._match(TokenType.SEPARATOR) and self._parse_field(), ), + # https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_values + "VALUES": lambda self: self.expression( + exp.Anonymous, this="VALUES", expressions=[self._parse_id_var()] + ), } STATEMENT_PARSERS = { @@ -399,6 +419,8 @@ class MySQL(Dialect): NULL_ORDERING_SUPPORTED = False JOIN_HINTS = False TABLE_HINTS = True + DUPLICATE_KEY_UPDATE_WITH_SET = False + QUERY_HINT_SEP = " " TRANSFORMS = { **generator.Generator.TRANSFORMS, @@ -445,6 +467,9 @@ class MySQL(Dialect): LIMIT_FETCH = "LIMIT" + def jsonarraycontains_sql(self, expression: exp.JSONArrayContains) -> str: + return f"{self.sql(expression, 'this')} MEMBER OF({self.sql(expression, 'expression')})" + def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: """(U)BIGINT is not allowed in a CAST expression, so we use (UN)SIGNED instead.""" if expression.to.this == exp.DataType.Type.BIGINT: diff --git a/sqlglot/dialects/oracle.py b/sqlglot/dialects/oracle.py index 8d35e92..2b77ef9 100644 --- a/sqlglot/dialects/oracle.py +++ b/sqlglot/dialects/oracle.py @@ -121,7 +121,6 @@ class Oracle(Dialect): "TO_DATE", e.this, exp.Literal.string("YYYY-MM-DD") ), exp.Group: transforms.preprocess([transforms.unalias_group]), - exp.Hint: lambda self, e: f" /*+ {self.expressions(e).strip()} */", exp.ILike: no_ilike_sql, exp.Coalesce: rename_func("NVL"), exp.Select: transforms.preprocess([transforms.eliminate_distinct_on]), diff --git a/sqlglot/dialects/postgres.py b/sqlglot/dialects/postgres.py index 766b584..6d78a07 100644 --- a/sqlglot/dialects/postgres.py +++ b/sqlglot/dialects/postgres.py @@ -183,6 +183,29 @@ def _to_timestamp(args: t.List) -> exp.Expression: return format_time_lambda(exp.StrToTime, "postgres")(args) +def _remove_target_from_merge(expression: exp.Expression) -> exp.Expression: + """Remove table refs from columns in when statements.""" + if isinstance(expression, exp.Merge): + alias = expression.this.args.get("alias") + + normalize = lambda identifier: Postgres.normalize_identifier(identifier).name + + targets = {normalize(expression.this.this)} + + if alias: + targets.add(normalize(alias.this)) + + for when in expression.expressions: + when.transform( + lambda node: exp.column(node.name) + if isinstance(node, exp.Column) and normalize(node.args.get("table")) in targets + else node, + copy=False, + ) + + return expression + + class Postgres(Dialect): INDEX_OFFSET = 1 NULL_ORDERING = "nulls_are_large" @@ -315,6 +338,7 @@ class Postgres(Dialect): LOCKING_READS_SUPPORTED = True JOIN_HINTS = False TABLE_HINTS = False + QUERY_HINTS = False PARAMETER_TOKEN = "$" TYPE_MAPPING = { @@ -352,7 +376,7 @@ class Postgres(Dialect): exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), exp.ArrayContains: lambda self, e: self.binary(e, "@>"), exp.ArrayContained: lambda self, e: self.binary(e, "<@"), - exp.Merge: transforms.preprocess([transforms.remove_target_from_merge]), + exp.Merge: transforms.preprocess([_remove_target_from_merge]), exp.Pivot: no_pivot_sql, exp.RegexpLike: lambda self, e: self.binary(e, "~"), exp.RegexpILike: lambda self, e: self.binary(e, "~*"), diff --git a/sqlglot/dialects/presto.py b/sqlglot/dialects/presto.py index 24c439b..1721588 100644 --- a/sqlglot/dialects/presto.py +++ b/sqlglot/dialects/presto.py @@ -232,6 +232,7 @@ class Presto(Dialect): INTERVAL_ALLOWS_PLURAL_FORM = False JOIN_HINTS = False TABLE_HINTS = False + QUERY_HINTS = False IS_BOOL_ALLOWED = False STRUCT_DELIMITER = ("(", ")") diff --git a/sqlglot/dialects/redshift.py b/sqlglot/dialects/redshift.py index 87be42c..09edd55 100644 --- a/sqlglot/dialects/redshift.py +++ b/sqlglot/dialects/redshift.py @@ -86,6 +86,7 @@ class Redshift(Postgres): class Generator(Postgres.Generator): LOCKING_READS_SUPPORTED = False RENAME_TABLE_WITH_DB = False + QUERY_HINTS = False TYPE_MAPPING = { **Postgres.Generator.TYPE_MAPPING, diff --git a/sqlglot/dialects/snowflake.py b/sqlglot/dialects/snowflake.py index 34e4dd0..19924cd 100644 --- a/sqlglot/dialects/snowflake.py +++ b/sqlglot/dialects/snowflake.py @@ -326,6 +326,7 @@ class Snowflake(Dialect): SINGLE_STRING_INTERVAL = True JOIN_HINTS = False TABLE_HINTS = False + QUERY_HINTS = False TRANSFORMS = { **generator.Generator.TRANSFORMS, diff --git a/sqlglot/dialects/spark2.py b/sqlglot/dialects/spark2.py index 3720b8d..afe2482 100644 --- a/sqlglot/dialects/spark2.py +++ b/sqlglot/dialects/spark2.py @@ -173,6 +173,8 @@ class Spark2(Hive): return pivot_column_names(aggregations, dialect="spark") class Generator(Hive.Generator): + QUERY_HINTS = True + TYPE_MAPPING = { **Hive.Generator.TYPE_MAPPING, exp.DataType.Type.TINYINT: "BYTE", @@ -203,7 +205,6 @@ class Spark2(Hive): exp.DayOfYear: rename_func("DAYOFYEAR"), exp.FileFormatProperty: lambda self, e: f"USING {e.name.upper()}", exp.From: transforms.preprocess([_unalias_pivot]), - exp.Hint: lambda self, e: f" /*+ {self.expressions(e).strip()} */", exp.LogicalAnd: rename_func("BOOL_AND"), exp.LogicalOr: rename_func("BOOL_OR"), exp.Map: _map_sql, diff --git a/sqlglot/dialects/sqlite.py b/sqlglot/dialects/sqlite.py index 519e62a..5ded6df 100644 --- a/sqlglot/dialects/sqlite.py +++ b/sqlglot/dialects/sqlite.py @@ -77,6 +77,7 @@ class SQLite(Dialect): class Generator(generator.Generator): JOIN_HINTS = False TABLE_HINTS = False + QUERY_HINTS = False TYPE_MAPPING = { **generator.Generator.TYPE_MAPPING, diff --git a/sqlglot/dialects/tableau.py b/sqlglot/dialects/tableau.py index 67ef76b..33ec7e1 100644 --- a/sqlglot/dialects/tableau.py +++ b/sqlglot/dialects/tableau.py @@ -8,6 +8,7 @@ class Tableau(Dialect): class Generator(generator.Generator): JOIN_HINTS = False TABLE_HINTS = False + QUERY_HINTS = False TRANSFORMS = { **generator.Generator.TRANSFORMS, diff --git a/sqlglot/dialects/teradata.py b/sqlglot/dialects/teradata.py index d9a5417..4e8ffb4 100644 --- a/sqlglot/dialects/teradata.py +++ b/sqlglot/dialects/teradata.py @@ -121,7 +121,7 @@ class Teradata(Dialect): exp.Update, **{ # type: ignore "this": self._parse_table(alias_tokens=self.UPDATE_ALIAS_TOKENS), - "from": self._parse_from(modifiers=True), + "from": self._parse_from(joins=True), "expressions": self._match(TokenType.SET) and self._parse_csv(self._parse_equality), "where": self._parse_where(), @@ -140,6 +140,7 @@ class Teradata(Dialect): class Generator(generator.Generator): JOIN_HINTS = False TABLE_HINTS = False + QUERY_HINTS = False TYPE_MAPPING = { **generator.Generator.TYPE_MAPPING, diff --git a/sqlglot/dialects/tsql.py b/sqlglot/dialects/tsql.py index f671630..92bb755 100644 --- a/sqlglot/dialects/tsql.py +++ b/sqlglot/dialects/tsql.py @@ -60,10 +60,10 @@ def _format_time_lambda( assert len(args) == 2 return exp_class( - this=args[1], + this=exp.cast(args[1], "datetime"), format=exp.Literal.string( format_time( - args[0].name, + args[0].name.lower(), {**TSQL.TIME_MAPPING, **FULL_FORMAT_TIME_MAPPING} if full_format_mapping else TSQL.TIME_MAPPING, @@ -467,6 +467,8 @@ class TSQL(Dialect): class Generator(generator.Generator): LOCKING_READS_SUPPORTED = True + LIMIT_IS_TOP = True + QUERY_HINTS = False TYPE_MAPPING = { **generator.Generator.TYPE_MAPPING, @@ -482,6 +484,7 @@ class TSQL(Dialect): exp.DateDiff: generate_date_delta_with_unit_sql, exp.CurrentDate: rename_func("GETDATE"), exp.CurrentTimestamp: rename_func("GETDATE"), + exp.Extract: rename_func("DATEPART"), exp.GroupConcat: _string_agg_sql, exp.If: rename_func("IIF"), exp.Max: max_or_greatest, |