sqlglot 27.7.0__py3-none-any.whl → 27.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlglot/_version.py +16 -3
- sqlglot/dialects/__init__.py +1 -1
- sqlglot/dialects/bigquery.py +129 -9
- sqlglot/dialects/clickhouse.py +11 -0
- sqlglot/dialects/databricks.py +5 -1
- sqlglot/dialects/dialect.py +74 -23
- sqlglot/dialects/doris.py +77 -9
- sqlglot/dialects/dremio.py +102 -21
- sqlglot/dialects/duckdb.py +20 -43
- sqlglot/dialects/exasol.py +28 -0
- sqlglot/dialects/mysql.py +0 -48
- sqlglot/dialects/presto.py +0 -2
- sqlglot/dialects/redshift.py +1 -0
- sqlglot/dialects/singlestore.py +252 -13
- sqlglot/dialects/spark.py +6 -0
- sqlglot/dialects/trino.py +1 -0
- sqlglot/dialects/tsql.py +2 -0
- sqlglot/expressions.py +143 -7
- sqlglot/generator.py +98 -27
- sqlglot/jsonpath.py +10 -3
- sqlglot/optimizer/qualify_columns.py +1 -1
- sqlglot/parser.py +58 -17
- {sqlglot-27.7.0.dist-info → sqlglot-27.9.0.dist-info}/METADATA +42 -2
- {sqlglot-27.7.0.dist-info → sqlglot-27.9.0.dist-info}/RECORD +27 -27
- {sqlglot-27.7.0.dist-info → sqlglot-27.9.0.dist-info}/WHEEL +0 -0
- {sqlglot-27.7.0.dist-info → sqlglot-27.9.0.dist-info}/licenses/LICENSE +0 -0
- {sqlglot-27.7.0.dist-info → sqlglot-27.9.0.dist-info}/top_level.txt +0 -0
sqlglot/dialects/doris.py
CHANGED
|
@@ -65,7 +65,11 @@ class Doris(MySQL):
|
|
|
65
65
|
**MySQL.Parser.PROPERTY_PARSERS,
|
|
66
66
|
"PROPERTIES": lambda self: self._parse_wrapped_properties(),
|
|
67
67
|
"UNIQUE": lambda self: self._parse_composite_key_property(exp.UniqueKeyProperty),
|
|
68
|
+
# Plain KEY without UNIQUE/DUPLICATE/AGGREGATE prefixes should be treated as UniqueKeyProperty with unique=False
|
|
69
|
+
"KEY": lambda self: self._parse_composite_key_property(exp.UniqueKeyProperty),
|
|
68
70
|
"PARTITION BY": lambda self: self._parse_partition_by_opt_range(),
|
|
71
|
+
"BUILD": lambda self: self._parse_build_property(),
|
|
72
|
+
"REFRESH": lambda self: self._parse_refresh_property(),
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
def _parse_partitioning_granularity_dynamic(self) -> exp.PartitionByRangePropertyDynamic:
|
|
@@ -104,9 +108,27 @@ class Doris(MySQL):
|
|
|
104
108
|
part_range = self.expression(exp.PartitionRange, this=name, expressions=values)
|
|
105
109
|
return self.expression(exp.Partition, expressions=[part_range])
|
|
106
110
|
|
|
111
|
+
def _parse_partition_definition_list(self) -> exp.Partition:
|
|
112
|
+
# PARTITION <name> VALUES IN (<value_csv>)
|
|
113
|
+
self._match_text_seq("PARTITION")
|
|
114
|
+
name = self._parse_id_var()
|
|
115
|
+
self._match_text_seq("VALUES", "IN")
|
|
116
|
+
values = self._parse_wrapped_csv(self._parse_expression)
|
|
117
|
+
part_list = self.expression(exp.PartitionList, this=name, expressions=values)
|
|
118
|
+
return self.expression(exp.Partition, expressions=[part_list])
|
|
119
|
+
|
|
107
120
|
def _parse_partition_by_opt_range(
|
|
108
121
|
self,
|
|
109
|
-
) -> exp.PartitionedByProperty | exp.PartitionByRangeProperty:
|
|
122
|
+
) -> exp.PartitionedByProperty | exp.PartitionByRangeProperty | exp.PartitionByListProperty:
|
|
123
|
+
if self._match_text_seq("LIST"):
|
|
124
|
+
return self.expression(
|
|
125
|
+
exp.PartitionByListProperty,
|
|
126
|
+
partition_expressions=self._parse_wrapped_id_vars(),
|
|
127
|
+
create_expressions=self._parse_wrapped_csv(
|
|
128
|
+
self._parse_partition_definition_list
|
|
129
|
+
),
|
|
130
|
+
)
|
|
131
|
+
|
|
110
132
|
if not self._match_text_seq("RANGE"):
|
|
111
133
|
return super()._parse_partitioned_by()
|
|
112
134
|
|
|
@@ -128,6 +150,28 @@ class Doris(MySQL):
|
|
|
128
150
|
create_expressions=create_expressions,
|
|
129
151
|
)
|
|
130
152
|
|
|
153
|
+
def _parse_build_property(self) -> exp.BuildProperty:
|
|
154
|
+
return self.expression(exp.BuildProperty, this=self._parse_var(upper=True))
|
|
155
|
+
|
|
156
|
+
def _parse_refresh_property(self) -> exp.RefreshTriggerProperty:
|
|
157
|
+
method = self._parse_var(upper=True)
|
|
158
|
+
|
|
159
|
+
self._match(TokenType.ON)
|
|
160
|
+
|
|
161
|
+
kind = self._match_texts(("MANUAL", "COMMIT", "SCHEDULE")) and self._prev.text.upper()
|
|
162
|
+
every = self._match_text_seq("EVERY") and self._parse_number()
|
|
163
|
+
unit = self._parse_var(any_token=True) if every else None
|
|
164
|
+
starts = self._match_text_seq("STARTS") and self._parse_string()
|
|
165
|
+
|
|
166
|
+
return self.expression(
|
|
167
|
+
exp.RefreshTriggerProperty,
|
|
168
|
+
method=method,
|
|
169
|
+
kind=kind,
|
|
170
|
+
every=every,
|
|
171
|
+
unit=unit,
|
|
172
|
+
starts=starts,
|
|
173
|
+
)
|
|
174
|
+
|
|
131
175
|
class Generator(MySQL.Generator):
|
|
132
176
|
LAST_DAY_SUPPORTS_DATE_PART = False
|
|
133
177
|
VARCHAR_REQUIRES_SIZE = False
|
|
@@ -145,7 +189,10 @@ class Doris(MySQL):
|
|
|
145
189
|
**MySQL.Generator.PROPERTIES_LOCATION,
|
|
146
190
|
exp.UniqueKeyProperty: exp.Properties.Location.POST_SCHEMA,
|
|
147
191
|
exp.PartitionByRangeProperty: exp.Properties.Location.POST_SCHEMA,
|
|
192
|
+
exp.PartitionByListProperty: exp.Properties.Location.POST_SCHEMA,
|
|
148
193
|
exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA,
|
|
194
|
+
exp.BuildProperty: exp.Properties.Location.POST_SCHEMA,
|
|
195
|
+
exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,
|
|
149
196
|
}
|
|
150
197
|
|
|
151
198
|
CAST_MAPPING = {}
|
|
@@ -662,9 +709,18 @@ class Doris(MySQL):
|
|
|
662
709
|
"year",
|
|
663
710
|
}
|
|
664
711
|
|
|
712
|
+
def uniquekeyproperty_sql(
|
|
713
|
+
self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
|
|
714
|
+
) -> str:
|
|
715
|
+
create_stmt = expression.find_ancestor(exp.Create)
|
|
716
|
+
if create_stmt and create_stmt.args["properties"].find(exp.MaterializedProperty):
|
|
717
|
+
return super().uniquekeyproperty_sql(expression, prefix="KEY")
|
|
718
|
+
|
|
719
|
+
return super().uniquekeyproperty_sql(expression)
|
|
720
|
+
|
|
665
721
|
def partition_sql(self, expression: exp.Partition) -> str:
|
|
666
722
|
parent = expression.parent
|
|
667
|
-
if isinstance(parent, exp.PartitionByRangeProperty):
|
|
723
|
+
if isinstance(parent, (exp.PartitionByRangeProperty, exp.PartitionByListProperty)):
|
|
668
724
|
return ", ".join(self.sql(e) for e in expression.expressions)
|
|
669
725
|
return super().partition_sql(expression)
|
|
670
726
|
|
|
@@ -685,7 +741,9 @@ class Doris(MySQL):
|
|
|
685
741
|
|
|
686
742
|
return f"PARTITION {name} VALUES LESS THAN ({self.sql(values[0])})"
|
|
687
743
|
|
|
688
|
-
def partitionbyrangepropertydynamic_sql(
|
|
744
|
+
def partitionbyrangepropertydynamic_sql(
|
|
745
|
+
self, expression: exp.PartitionByRangePropertyDynamic
|
|
746
|
+
) -> str:
|
|
689
747
|
# Generates: FROM ("start") TO ("end") INTERVAL N UNIT
|
|
690
748
|
start = self.sql(expression, "start")
|
|
691
749
|
end = self.sql(expression, "end")
|
|
@@ -699,15 +757,25 @@ class Doris(MySQL):
|
|
|
699
757
|
|
|
700
758
|
return f"FROM ({start}) TO ({end}) {interval}"
|
|
701
759
|
|
|
702
|
-
def partitionbyrangeproperty_sql(self, expression):
|
|
703
|
-
partition_expressions =
|
|
704
|
-
|
|
760
|
+
def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
|
|
761
|
+
partition_expressions = self.expressions(
|
|
762
|
+
expression, key="partition_expressions", indent=False
|
|
705
763
|
)
|
|
706
|
-
|
|
707
|
-
# Handle both static and dynamic partition definitions
|
|
708
|
-
create_sql = ", ".join(self.sql(e) for e in create_expressions)
|
|
764
|
+
create_sql = self.expressions(expression, key="create_expressions", indent=False)
|
|
709
765
|
return f"PARTITION BY RANGE ({partition_expressions}) ({create_sql})"
|
|
710
766
|
|
|
767
|
+
def partitionbylistproperty_sql(self, expression: exp.PartitionByListProperty) -> str:
|
|
768
|
+
partition_expressions = self.expressions(
|
|
769
|
+
expression, key="partition_expressions", indent=False
|
|
770
|
+
)
|
|
771
|
+
create_sql = self.expressions(expression, key="create_expressions", indent=False)
|
|
772
|
+
return f"PARTITION BY LIST ({partition_expressions}) ({create_sql})"
|
|
773
|
+
|
|
774
|
+
def partitionlist_sql(self, expression: exp.PartitionList) -> str:
|
|
775
|
+
name = self.sql(expression, "this")
|
|
776
|
+
values = self.expressions(expression, indent=False)
|
|
777
|
+
return f"PARTITION {name} VALUES IN ({values})"
|
|
778
|
+
|
|
711
779
|
def partitionedbyproperty_sql(self, expression: exp.PartitionedByProperty) -> str:
|
|
712
780
|
node = expression.this
|
|
713
781
|
if isinstance(node, exp.Schema):
|
sqlglot/dialects/dremio.py
CHANGED
|
@@ -1,36 +1,79 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import typing as t
|
|
3
4
|
from sqlglot import expressions as exp
|
|
4
5
|
from sqlglot import parser, generator, tokens
|
|
5
|
-
from sqlglot.dialects.dialect import
|
|
6
|
-
|
|
6
|
+
from sqlglot.dialects.dialect import (
|
|
7
|
+
Dialect,
|
|
8
|
+
build_timetostr_or_tochar,
|
|
9
|
+
build_formatted_time,
|
|
10
|
+
build_date_delta,
|
|
11
|
+
rename_func,
|
|
12
|
+
)
|
|
13
|
+
from sqlglot.helper import seq_get
|
|
14
|
+
from sqlglot.tokens import TokenType
|
|
7
15
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
]
|
|
16
|
+
if t.TYPE_CHECKING:
|
|
17
|
+
from sqlglot.dialects.dialect import DialectType
|
|
18
|
+
|
|
19
|
+
DATE_DELTA = t.Union[exp.DateAdd, exp.DateSub]
|
|
12
20
|
|
|
13
21
|
|
|
14
22
|
def _date_delta_sql(name: str) -> t.Callable[[Dremio.Generator, DATE_DELTA], str]:
|
|
15
23
|
def _delta_sql(self: Dremio.Generator, expression: DATE_DELTA) -> str:
|
|
16
|
-
unit = expression.text("unit")
|
|
24
|
+
unit = expression.text("unit").upper()
|
|
17
25
|
|
|
18
|
-
|
|
26
|
+
# Fallback to default behavior if unit is missing or 'DAY'
|
|
27
|
+
if not unit or unit == "DAY":
|
|
19
28
|
return self.func(name, expression.this, expression.expression)
|
|
20
29
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
increment = exp.Literal.number(value * -1)
|
|
27
|
-
else:
|
|
28
|
-
increment *= exp.Literal.number(-1)
|
|
29
|
-
return self.func("TIMESTAMPADD", unit_to_var(expression), increment, expression.this)
|
|
30
|
+
this_sql = self.sql(expression, "this")
|
|
31
|
+
expr_sql = self.sql(expression, "expression")
|
|
32
|
+
|
|
33
|
+
interval_sql = f"CAST({expr_sql} AS INTERVAL {unit})"
|
|
34
|
+
return f"{name}({this_sql}, {interval_sql})"
|
|
30
35
|
|
|
31
36
|
return _delta_sql
|
|
32
37
|
|
|
33
38
|
|
|
39
|
+
def to_char_is_numeric_handler(args: t.List, dialect: DialectType) -> exp.TimeToStr | exp.ToChar:
|
|
40
|
+
expression = build_timetostr_or_tochar(args, dialect)
|
|
41
|
+
fmt = seq_get(args, 1)
|
|
42
|
+
|
|
43
|
+
if fmt and isinstance(expression, exp.ToChar) and fmt.is_string and "#" in fmt.name:
|
|
44
|
+
# Only mark as numeric if format is a literal containing #
|
|
45
|
+
expression.set("is_numeric", True)
|
|
46
|
+
|
|
47
|
+
return expression
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def build_date_delta_with_cast_interval(
|
|
51
|
+
expression_class: t.Type[DATE_DELTA],
|
|
52
|
+
) -> t.Callable[[t.List[exp.Expression]], exp.Expression]:
|
|
53
|
+
fallback_builder = build_date_delta(expression_class)
|
|
54
|
+
|
|
55
|
+
def _builder(args):
|
|
56
|
+
if len(args) == 2:
|
|
57
|
+
date_arg, interval_arg = args
|
|
58
|
+
|
|
59
|
+
if (
|
|
60
|
+
isinstance(interval_arg, exp.Cast)
|
|
61
|
+
and isinstance(interval_arg.to, exp.DataType)
|
|
62
|
+
and isinstance(interval_arg.to.this, exp.Interval)
|
|
63
|
+
):
|
|
64
|
+
return expression_class(
|
|
65
|
+
this=date_arg,
|
|
66
|
+
expression=interval_arg.this,
|
|
67
|
+
unit=interval_arg.to.this.unit,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return expression_class(this=date_arg, expression=interval_arg)
|
|
71
|
+
|
|
72
|
+
return fallback_builder(args)
|
|
73
|
+
|
|
74
|
+
return _builder
|
|
75
|
+
|
|
76
|
+
|
|
34
77
|
class Dremio(Dialect):
|
|
35
78
|
SUPPORTS_USER_DEFINED_TYPES = False
|
|
36
79
|
CONCAT_COALESCE = True
|
|
@@ -89,14 +132,39 @@ class Dremio(Dialect):
|
|
|
89
132
|
"tzo": "%z", # numeric offset (+0200)
|
|
90
133
|
}
|
|
91
134
|
|
|
135
|
+
class Tokenizer(tokens.Tokenizer):
|
|
136
|
+
COMMENTS = ["--", "//", ("/*", "*/")]
|
|
137
|
+
|
|
92
138
|
class Parser(parser.Parser):
|
|
93
139
|
LOG_DEFAULTS_TO_LN = True
|
|
94
140
|
|
|
141
|
+
NO_PAREN_FUNCTION_PARSERS = {
|
|
142
|
+
**parser.Parser.NO_PAREN_FUNCTION_PARSERS,
|
|
143
|
+
"CURRENT_DATE_UTC": lambda self: self._parse_current_date_utc(),
|
|
144
|
+
}
|
|
145
|
+
|
|
95
146
|
FUNCTIONS = {
|
|
96
147
|
**parser.Parser.FUNCTIONS,
|
|
97
|
-
"TO_CHAR":
|
|
148
|
+
"TO_CHAR": to_char_is_numeric_handler,
|
|
149
|
+
"DATE_FORMAT": build_formatted_time(exp.TimeToStr, "dremio"),
|
|
150
|
+
"TO_DATE": build_formatted_time(exp.TsOrDsToDate, "dremio"),
|
|
151
|
+
"DATE_ADD": build_date_delta_with_cast_interval(exp.DateAdd),
|
|
152
|
+
"DATE_SUB": build_date_delta_with_cast_interval(exp.DateSub),
|
|
153
|
+
"ARRAY_GENERATE_RANGE": exp.GenerateSeries.from_arg_list,
|
|
98
154
|
}
|
|
99
155
|
|
|
156
|
+
def _parse_current_date_utc(self) -> exp.Cast:
|
|
157
|
+
if self._match(TokenType.L_PAREN):
|
|
158
|
+
self._match_r_paren()
|
|
159
|
+
|
|
160
|
+
return exp.Cast(
|
|
161
|
+
this=exp.AtTimeZone(
|
|
162
|
+
this=exp.CurrentTimestamp(),
|
|
163
|
+
zone=exp.Literal.string("UTC"),
|
|
164
|
+
),
|
|
165
|
+
to=exp.DataType.build("DATE"),
|
|
166
|
+
)
|
|
167
|
+
|
|
100
168
|
class Generator(generator.Generator):
|
|
101
169
|
NVL2_SUPPORTED = False
|
|
102
170
|
SUPPORTS_CONVERT_TIMEZONE = True
|
|
@@ -123,10 +191,11 @@ class Dremio(Dialect):
|
|
|
123
191
|
|
|
124
192
|
TRANSFORMS = {
|
|
125
193
|
**generator.Generator.TRANSFORMS,
|
|
194
|
+
exp.ToChar: rename_func("TO_CHAR"),
|
|
126
195
|
exp.TimeToStr: lambda self, e: self.func("TO_CHAR", e.this, self.format_time(e)),
|
|
127
|
-
exp.ToChar: lambda self, e: self.function_fallback_sql(e),
|
|
128
196
|
exp.DateAdd: _date_delta_sql("DATE_ADD"),
|
|
129
197
|
exp.DateSub: _date_delta_sql("DATE_SUB"),
|
|
198
|
+
exp.GenerateSeries: rename_func("ARRAY_GENERATE_RANGE"),
|
|
130
199
|
}
|
|
131
200
|
|
|
132
201
|
def datatype_sql(self, expression: exp.DataType) -> str:
|
|
@@ -141,5 +210,17 @@ class Dremio(Dialect):
|
|
|
141
210
|
|
|
142
211
|
return super().datatype_sql(expression)
|
|
143
212
|
|
|
144
|
-
|
|
145
|
-
|
|
213
|
+
def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
|
|
214
|
+
# Match: CAST(CURRENT_TIMESTAMP AT TIME ZONE 'UTC' AS DATE)
|
|
215
|
+
if expression.is_type(exp.DataType.Type.DATE):
|
|
216
|
+
at_time_zone = expression.this
|
|
217
|
+
|
|
218
|
+
if (
|
|
219
|
+
isinstance(at_time_zone, exp.AtTimeZone)
|
|
220
|
+
and isinstance(at_time_zone.this, exp.CurrentTimestamp)
|
|
221
|
+
and isinstance(at_time_zone.args["zone"], exp.Literal)
|
|
222
|
+
and at_time_zone.text("zone").upper() == "UTC"
|
|
223
|
+
):
|
|
224
|
+
return "CURRENT_DATE_UTC"
|
|
225
|
+
|
|
226
|
+
return super().cast_sql(expression, safe_prefix)
|
sqlglot/dialects/duckdb.py
CHANGED
|
@@ -4,7 +4,6 @@ import typing as t
|
|
|
4
4
|
|
|
5
5
|
from sqlglot import exp, generator, parser, tokens, transforms
|
|
6
6
|
|
|
7
|
-
from sqlglot.expressions import DATA_TYPE
|
|
8
7
|
from sqlglot.dialects.dialect import (
|
|
9
8
|
Dialect,
|
|
10
9
|
JSON_EXTRACT_TYPE,
|
|
@@ -16,6 +15,7 @@ from sqlglot.dialects.dialect import (
|
|
|
16
15
|
bool_xor_sql,
|
|
17
16
|
build_default_decimal_type,
|
|
18
17
|
count_if_to_sum,
|
|
18
|
+
date_delta_to_binary_interval_op,
|
|
19
19
|
date_trunc_to_time,
|
|
20
20
|
datestrtodate_sql,
|
|
21
21
|
no_datetime_sql,
|
|
@@ -32,7 +32,6 @@ from sqlglot.dialects.dialect import (
|
|
|
32
32
|
str_to_time_sql,
|
|
33
33
|
timestamptrunc_sql,
|
|
34
34
|
timestrtotime_sql,
|
|
35
|
-
unit_to_var,
|
|
36
35
|
unit_to_str,
|
|
37
36
|
sha256_sql,
|
|
38
37
|
build_regexp_extract,
|
|
@@ -45,38 +44,6 @@ from sqlglot.helper import seq_get
|
|
|
45
44
|
from sqlglot.tokens import TokenType
|
|
46
45
|
from sqlglot.parser import binary_range_parser
|
|
47
46
|
|
|
48
|
-
DATETIME_DELTA = t.Union[
|
|
49
|
-
exp.DateAdd, exp.TimeAdd, exp.DatetimeAdd, exp.TsOrDsAdd, exp.DateSub, exp.DatetimeSub
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def _date_delta_sql(self: DuckDB.Generator, expression: DATETIME_DELTA) -> str:
|
|
54
|
-
this = expression.this
|
|
55
|
-
unit = unit_to_var(expression)
|
|
56
|
-
op = (
|
|
57
|
-
"+"
|
|
58
|
-
if isinstance(expression, (exp.DateAdd, exp.TimeAdd, exp.DatetimeAdd, exp.TsOrDsAdd))
|
|
59
|
-
else "-"
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
to_type: t.Optional[DATA_TYPE] = None
|
|
63
|
-
if isinstance(expression, exp.TsOrDsAdd):
|
|
64
|
-
to_type = expression.return_type
|
|
65
|
-
elif this.is_string:
|
|
66
|
-
# Cast string literals (i.e function parameters) to the appropriate type for +/- interval to work
|
|
67
|
-
to_type = (
|
|
68
|
-
exp.DataType.Type.DATETIME
|
|
69
|
-
if isinstance(expression, (exp.DatetimeAdd, exp.DatetimeSub))
|
|
70
|
-
else exp.DataType.Type.DATE
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
this = exp.cast(this, to_type) if to_type else this
|
|
74
|
-
|
|
75
|
-
expr = expression.expression
|
|
76
|
-
interval = expr if isinstance(expr, exp.Interval) else exp.Interval(this=expr, unit=unit)
|
|
77
|
-
|
|
78
|
-
return f"{self.sql(this)} {op} {self.sql(interval)}"
|
|
79
|
-
|
|
80
47
|
|
|
81
48
|
# BigQuery -> DuckDB conversion for the DATE function
|
|
82
49
|
def _date_sql(self: DuckDB.Generator, expression: exp.Date) -> str:
|
|
@@ -419,10 +386,12 @@ class DuckDB(Dialect):
|
|
|
419
386
|
"JSON_EXTRACT_PATH": parser.build_extract_json_with_path(exp.JSONExtract),
|
|
420
387
|
"JSON_EXTRACT_STRING": parser.build_extract_json_with_path(exp.JSONExtractScalar),
|
|
421
388
|
"LIST_CONTAINS": exp.ArrayContains.from_arg_list,
|
|
389
|
+
"LIST_FILTER": exp.ArrayFilter.from_arg_list,
|
|
422
390
|
"LIST_HAS": exp.ArrayContains.from_arg_list,
|
|
423
391
|
"LIST_HAS_ANY": exp.ArrayOverlaps.from_arg_list,
|
|
424
392
|
"LIST_REVERSE_SORT": _build_sort_array_desc,
|
|
425
393
|
"LIST_SORT": exp.SortArray.from_arg_list,
|
|
394
|
+
"LIST_TRANSFORM": exp.Transform.from_arg_list,
|
|
426
395
|
"LIST_VALUE": lambda args: exp.Array(expressions=args),
|
|
427
396
|
"MAKE_TIME": exp.TimeFromParts.from_arg_list,
|
|
428
397
|
"MAKE_TIMESTAMP": _build_make_timestamp,
|
|
@@ -676,6 +645,9 @@ class DuckDB(Dialect):
|
|
|
676
645
|
exp.ArrayRemove: remove_from_array_using_filter,
|
|
677
646
|
exp.ArraySort: _array_sort_sql,
|
|
678
647
|
exp.ArraySum: rename_func("LIST_SUM"),
|
|
648
|
+
exp.ArrayUniqueAgg: lambda self, e: self.func(
|
|
649
|
+
"LIST", exp.Distinct(expressions=[e.this])
|
|
650
|
+
),
|
|
679
651
|
exp.BitwiseXor: rename_func("XOR"),
|
|
680
652
|
exp.CommentColumnConstraint: no_comment_column_constraint_sql,
|
|
681
653
|
exp.CurrentDate: lambda *_: "CURRENT_DATE",
|
|
@@ -687,14 +659,14 @@ class DuckDB(Dialect):
|
|
|
687
659
|
exp.DayOfYear: rename_func("DAYOFYEAR"),
|
|
688
660
|
exp.DataType: _datatype_sql,
|
|
689
661
|
exp.Date: _date_sql,
|
|
690
|
-
exp.DateAdd:
|
|
662
|
+
exp.DateAdd: date_delta_to_binary_interval_op(),
|
|
691
663
|
exp.DateFromParts: rename_func("MAKE_DATE"),
|
|
692
|
-
exp.DateSub:
|
|
664
|
+
exp.DateSub: date_delta_to_binary_interval_op(),
|
|
693
665
|
exp.DateDiff: _date_diff_sql,
|
|
694
666
|
exp.DateStrToDate: datestrtodate_sql,
|
|
695
667
|
exp.Datetime: no_datetime_sql,
|
|
696
|
-
exp.DatetimeSub:
|
|
697
|
-
exp.DatetimeAdd:
|
|
668
|
+
exp.DatetimeSub: date_delta_to_binary_interval_op(),
|
|
669
|
+
exp.DatetimeAdd: date_delta_to_binary_interval_op(),
|
|
698
670
|
exp.DateToDi: lambda self,
|
|
699
671
|
e: f"CAST(STRFTIME({self.sql(e, 'this')}, {DuckDB.DATEINT_FORMAT}) AS INT)",
|
|
700
672
|
exp.Decode: lambda self, e: encode_decode_sql(self, e, "DECODE", replace=False),
|
|
@@ -756,7 +728,7 @@ class DuckDB(Dialect):
|
|
|
756
728
|
),
|
|
757
729
|
exp.Struct: _struct_sql,
|
|
758
730
|
exp.Transform: rename_func("LIST_TRANSFORM"),
|
|
759
|
-
exp.TimeAdd:
|
|
731
|
+
exp.TimeAdd: date_delta_to_binary_interval_op(),
|
|
760
732
|
exp.Time: no_time_sql,
|
|
761
733
|
exp.TimeDiff: _timediff_sql,
|
|
762
734
|
exp.Timestamp: no_timestamp_sql,
|
|
@@ -773,7 +745,7 @@ class DuckDB(Dialect):
|
|
|
773
745
|
exp.TimeToUnix: rename_func("EPOCH"),
|
|
774
746
|
exp.TsOrDiToDi: lambda self,
|
|
775
747
|
e: f"CAST(SUBSTR(REPLACE(CAST({self.sql(e, 'this')} AS TEXT), '-', ''), 1, 8) AS INT)",
|
|
776
|
-
exp.TsOrDsAdd:
|
|
748
|
+
exp.TsOrDsAdd: date_delta_to_binary_interval_op(),
|
|
777
749
|
exp.TsOrDsDiff: lambda self, e: self.func(
|
|
778
750
|
"DATE_DIFF",
|
|
779
751
|
f"'{e.args.get('unit') or 'DAY'}'",
|
|
@@ -1150,15 +1122,20 @@ class DuckDB(Dialect):
|
|
|
1150
1122
|
return super().unnest_sql(expression)
|
|
1151
1123
|
|
|
1152
1124
|
def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
|
|
1153
|
-
|
|
1125
|
+
this = expression.this
|
|
1126
|
+
|
|
1127
|
+
if isinstance(this, self.IGNORE_RESPECT_NULLS_WINDOW_FUNCTIONS):
|
|
1154
1128
|
# DuckDB should render IGNORE NULLS only for the general-purpose
|
|
1155
1129
|
# window functions that accept it e.g. FIRST_VALUE(... IGNORE NULLS) OVER (...)
|
|
1156
1130
|
return super().ignorenulls_sql(expression)
|
|
1157
1131
|
|
|
1158
|
-
if
|
|
1132
|
+
if isinstance(this, exp.First):
|
|
1133
|
+
this = exp.AnyValue(this=this.this)
|
|
1134
|
+
|
|
1135
|
+
if not isinstance(this, exp.AnyValue):
|
|
1159
1136
|
self.unsupported("IGNORE NULLS is not supported for non-window functions.")
|
|
1160
1137
|
|
|
1161
|
-
return self.sql(
|
|
1138
|
+
return self.sql(this)
|
|
1162
1139
|
|
|
1163
1140
|
def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
|
|
1164
1141
|
if isinstance(expression.this, self.IGNORE_RESPECT_NULLS_WINDOW_FUNCTIONS):
|
sqlglot/dialects/exasol.py
CHANGED
|
@@ -5,8 +5,10 @@ import typing as t
|
|
|
5
5
|
from sqlglot import exp, generator, parser, tokens
|
|
6
6
|
from sqlglot.dialects.dialect import (
|
|
7
7
|
Dialect,
|
|
8
|
+
NormalizationStrategy,
|
|
8
9
|
binary_from_function,
|
|
9
10
|
build_formatted_time,
|
|
11
|
+
groupconcat_sql,
|
|
10
12
|
rename_func,
|
|
11
13
|
strposition_sql,
|
|
12
14
|
timestrtotime_sql,
|
|
@@ -73,6 +75,17 @@ DATE_UNITS = {"DAY", "WEEK", "MONTH", "YEAR", "HOUR", "MINUTE", "SECOND"}
|
|
|
73
75
|
|
|
74
76
|
|
|
75
77
|
class Exasol(Dialect):
|
|
78
|
+
# https://docs.exasol.com/db/latest/sql_references/basiclanguageelements.htm#SQLidentifier
|
|
79
|
+
NORMALIZATION_STRATEGY = NormalizationStrategy.UPPERCASE
|
|
80
|
+
# https://docs.exasol.com/db/latest/sql_references/data_types/datatypesoverview.htm
|
|
81
|
+
SUPPORTS_USER_DEFINED_TYPES = False
|
|
82
|
+
# https://docs.exasol.com/db/latest/sql/select.htm
|
|
83
|
+
SUPPORTS_SEMI_ANTI_JOIN = False
|
|
84
|
+
SUPPORTS_COLUMN_JOIN_MARKS = True
|
|
85
|
+
NULL_ORDERING = "nulls_are_last"
|
|
86
|
+
# https://docs.exasol.com/db/latest/sql_references/literals.htm#StringLiterals
|
|
87
|
+
CONCAT_COALESCE = True
|
|
88
|
+
|
|
76
89
|
TIME_MAPPING = {
|
|
77
90
|
"yyyy": "%Y",
|
|
78
91
|
"YYYY": "%Y",
|
|
@@ -108,7 +121,9 @@ class Exasol(Dialect):
|
|
|
108
121
|
# https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/if.htm
|
|
109
122
|
"ENDIF": TokenType.END,
|
|
110
123
|
"LONG VARCHAR": TokenType.TEXT,
|
|
124
|
+
"SEPARATOR": TokenType.SEPARATOR,
|
|
111
125
|
}
|
|
126
|
+
KEYWORDS.pop("DIV")
|
|
112
127
|
|
|
113
128
|
class Parser(parser.Parser):
|
|
114
129
|
FUNCTIONS = {
|
|
@@ -131,6 +146,7 @@ class Exasol(Dialect):
|
|
|
131
146
|
"DATE_TRUNC": lambda args: exp.TimestampTrunc(
|
|
132
147
|
this=seq_get(args, 1), unit=seq_get(args, 0)
|
|
133
148
|
),
|
|
149
|
+
"DIV": binary_from_function(exp.IntDiv),
|
|
134
150
|
"EVERY": lambda args: exp.All(this=seq_get(args, 0)),
|
|
135
151
|
"EDIT_DISTANCE": exp.Levenshtein.from_arg_list,
|
|
136
152
|
"HASH_SHA": exp.SHA.from_arg_list,
|
|
@@ -174,6 +190,12 @@ class Exasol(Dialect):
|
|
|
174
190
|
this=self._match(TokenType.IS) and self._parse_string(),
|
|
175
191
|
),
|
|
176
192
|
}
|
|
193
|
+
FUNCTION_PARSERS = {
|
|
194
|
+
**parser.Parser.FUNCTION_PARSERS,
|
|
195
|
+
# https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/listagg.htm
|
|
196
|
+
# https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/group_concat.htm
|
|
197
|
+
**dict.fromkeys(("GROUP_CONCAT", "LISTAGG"), lambda self: self._parse_group_concat()),
|
|
198
|
+
}
|
|
177
199
|
|
|
178
200
|
class Generator(generator.Generator):
|
|
179
201
|
# https://docs.exasol.com/db/latest/sql_references/data_types/datatypedetails.htm#StringDataType
|
|
@@ -228,9 +250,14 @@ class Exasol(Dialect):
|
|
|
228
250
|
# https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/bit_xor.htm
|
|
229
251
|
exp.BitwiseXor: rename_func("BIT_XOR"),
|
|
230
252
|
exp.DateDiff: _date_diff_sql,
|
|
253
|
+
# https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/div.htm#DIV
|
|
254
|
+
exp.IntDiv: rename_func("DIV"),
|
|
231
255
|
exp.TsOrDsDiff: _date_diff_sql,
|
|
232
256
|
exp.DateTrunc: lambda self, e: self.func("TRUNC", e.this, unit_to_str(e)),
|
|
233
257
|
exp.DatetimeTrunc: timestamptrunc_sql(),
|
|
258
|
+
exp.GroupConcat: lambda self, e: groupconcat_sql(
|
|
259
|
+
self, e, func_name="LISTAGG", within_group=True
|
|
260
|
+
),
|
|
234
261
|
# https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/edit_distance.htm#EDIT_DISTANCE
|
|
235
262
|
exp.Levenshtein: unsupported_args("ins_cost", "del_cost", "sub_cost", "max_dist")(
|
|
236
263
|
rename_func("EDIT_DISTANCE")
|
|
@@ -280,6 +307,7 @@ class Exasol(Dialect):
|
|
|
280
307
|
exp.MD5Digest: rename_func("HASHTYPE_MD5"),
|
|
281
308
|
# https://docs.exasol.com/db/latest/sql/create_view.htm
|
|
282
309
|
exp.CommentColumnConstraint: lambda self, e: f"COMMENT IS {self.sql(e, 'this')}",
|
|
310
|
+
exp.WeekOfYear: rename_func("WEEK"),
|
|
283
311
|
}
|
|
284
312
|
|
|
285
313
|
def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
|
sqlglot/dialects/mysql.py
CHANGED
|
@@ -676,54 +676,6 @@ class MySQL(Dialect):
|
|
|
676
676
|
parse_interval=parse_interval, fallback_to_identifier=fallback_to_identifier
|
|
677
677
|
)
|
|
678
678
|
|
|
679
|
-
def _parse_group_concat(self) -> t.Optional[exp.Expression]:
|
|
680
|
-
def concat_exprs(
|
|
681
|
-
node: t.Optional[exp.Expression], exprs: t.List[exp.Expression]
|
|
682
|
-
) -> exp.Expression:
|
|
683
|
-
if isinstance(node, exp.Distinct) and len(node.expressions) > 1:
|
|
684
|
-
concat_exprs = [
|
|
685
|
-
self.expression(exp.Concat, expressions=node.expressions, safe=True)
|
|
686
|
-
]
|
|
687
|
-
node.set("expressions", concat_exprs)
|
|
688
|
-
return node
|
|
689
|
-
if len(exprs) == 1:
|
|
690
|
-
return exprs[0]
|
|
691
|
-
return self.expression(exp.Concat, expressions=args, safe=True)
|
|
692
|
-
|
|
693
|
-
args = self._parse_csv(self._parse_lambda)
|
|
694
|
-
|
|
695
|
-
if args:
|
|
696
|
-
order = args[-1] if isinstance(args[-1], exp.Order) else None
|
|
697
|
-
|
|
698
|
-
if order:
|
|
699
|
-
# Order By is the last (or only) expression in the list and has consumed the 'expr' before it,
|
|
700
|
-
# remove 'expr' from exp.Order and add it back to args
|
|
701
|
-
args[-1] = order.this
|
|
702
|
-
order.set("this", concat_exprs(order.this, args))
|
|
703
|
-
|
|
704
|
-
this = order or concat_exprs(args[0], args)
|
|
705
|
-
else:
|
|
706
|
-
this = None
|
|
707
|
-
|
|
708
|
-
separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None
|
|
709
|
-
|
|
710
|
-
return self.expression(exp.GroupConcat, this=this, separator=separator)
|
|
711
|
-
|
|
712
|
-
def _parse_json_value(self) -> exp.JSONValue:
|
|
713
|
-
this = self._parse_bitwise()
|
|
714
|
-
self._match(TokenType.COMMA)
|
|
715
|
-
path = self._parse_bitwise()
|
|
716
|
-
|
|
717
|
-
returning = self._match(TokenType.RETURNING) and self._parse_type()
|
|
718
|
-
|
|
719
|
-
return self.expression(
|
|
720
|
-
exp.JSONValue,
|
|
721
|
-
this=this,
|
|
722
|
-
path=self.dialect.to_json_path(path),
|
|
723
|
-
returning=returning,
|
|
724
|
-
on_condition=self._parse_on_condition(),
|
|
725
|
-
)
|
|
726
|
-
|
|
727
679
|
def _parse_alter_table_alter_index(self) -> exp.AlterIndex:
|
|
728
680
|
index = self._parse_field(any_token=True)
|
|
729
681
|
|
sqlglot/dialects/presto.py
CHANGED
|
@@ -31,7 +31,6 @@ from sqlglot.dialects.dialect import (
|
|
|
31
31
|
sequence_sql,
|
|
32
32
|
build_regexp_extract,
|
|
33
33
|
explode_to_unnest_sql,
|
|
34
|
-
space_sql,
|
|
35
34
|
)
|
|
36
35
|
from sqlglot.dialects.hive import Hive
|
|
37
36
|
from sqlglot.dialects.mysql import MySQL
|
|
@@ -506,7 +505,6 @@ class Presto(Dialect):
|
|
|
506
505
|
amend_exploded_column_table,
|
|
507
506
|
]
|
|
508
507
|
),
|
|
509
|
-
exp.Space: space_sql,
|
|
510
508
|
exp.SortArray: _no_sort_array,
|
|
511
509
|
exp.StrPosition: lambda self, e: strposition_sql(self, e, supports_occurrence=True),
|
|
512
510
|
exp.StrToDate: lambda self, e: f"CAST({_str_to_time_sql(self, e)} AS DATE)",
|
sqlglot/dialects/redshift.py
CHANGED
|
@@ -192,6 +192,7 @@ class Redshift(Postgres):
|
|
|
192
192
|
exp.DistKeyProperty: lambda self, e: self.func("DISTKEY", e.this),
|
|
193
193
|
exp.DistStyleProperty: lambda self, e: self.naked_property(e),
|
|
194
194
|
exp.Explode: lambda self, e: self.explode_sql(e),
|
|
195
|
+
exp.FarmFingerprint: rename_func("FARMFINGERPRINT64"),
|
|
195
196
|
exp.FromBase: rename_func("STRTOL"),
|
|
196
197
|
exp.GeneratedAsIdentityColumnConstraint: generatedasidentitycolumnconstraint_sql,
|
|
197
198
|
exp.JSONExtract: json_extract_segments("JSON_EXTRACT_PATH_TEXT"),
|