altimate-code 0.5.2 → 0.5.3

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.
Files changed (101) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/altimate +6 -0
  3. package/bin/altimate-code +6 -0
  4. package/dbt-tools/bin/altimate-dbt +2 -0
  5. package/dbt-tools/dist/altimate_python_packages/altimate_packages/altimate/__init__.py +0 -0
  6. package/dbt-tools/dist/altimate_python_packages/altimate_packages/altimate/fetch_schema.py +35 -0
  7. package/dbt-tools/dist/altimate_python_packages/altimate_packages/altimate/utils.py +353 -0
  8. package/dbt-tools/dist/altimate_python_packages/altimate_packages/altimate/validate_sql.py +114 -0
  9. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/__init__.py +178 -0
  10. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/__main__.py +96 -0
  11. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/_typing.py +17 -0
  12. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/__init__.py +3 -0
  13. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/__init__.py +18 -0
  14. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/_typing.py +18 -0
  15. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/column.py +332 -0
  16. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/dataframe.py +866 -0
  17. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/functions.py +1267 -0
  18. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/group.py +59 -0
  19. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/normalize.py +78 -0
  20. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/operations.py +53 -0
  21. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/readwriter.py +108 -0
  22. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/session.py +190 -0
  23. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/transforms.py +9 -0
  24. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/types.py +212 -0
  25. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/util.py +32 -0
  26. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/window.py +134 -0
  27. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/__init__.py +118 -0
  28. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/athena.py +166 -0
  29. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/bigquery.py +1331 -0
  30. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/clickhouse.py +1393 -0
  31. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/databricks.py +131 -0
  32. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/dialect.py +1915 -0
  33. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/doris.py +561 -0
  34. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/drill.py +157 -0
  35. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/druid.py +20 -0
  36. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/duckdb.py +1159 -0
  37. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/dune.py +16 -0
  38. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/hive.py +787 -0
  39. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/materialize.py +94 -0
  40. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/mysql.py +1324 -0
  41. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/oracle.py +378 -0
  42. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/postgres.py +778 -0
  43. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/presto.py +788 -0
  44. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/prql.py +203 -0
  45. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/redshift.py +448 -0
  46. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/risingwave.py +78 -0
  47. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/snowflake.py +1464 -0
  48. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/spark.py +202 -0
  49. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/spark2.py +349 -0
  50. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/sqlite.py +320 -0
  51. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/starrocks.py +343 -0
  52. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/tableau.py +61 -0
  53. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/teradata.py +356 -0
  54. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/trino.py +115 -0
  55. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/tsql.py +1403 -0
  56. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/diff.py +456 -0
  57. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/errors.py +93 -0
  58. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/executor/__init__.py +95 -0
  59. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/executor/context.py +101 -0
  60. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/executor/env.py +246 -0
  61. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/executor/python.py +460 -0
  62. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/executor/table.py +155 -0
  63. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/expressions.py +8870 -0
  64. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/generator.py +4993 -0
  65. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/helper.py +582 -0
  66. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/jsonpath.py +227 -0
  67. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/lineage.py +423 -0
  68. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/__init__.py +11 -0
  69. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/annotate_types.py +589 -0
  70. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/canonicalize.py +222 -0
  71. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/eliminate_ctes.py +43 -0
  72. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/eliminate_joins.py +181 -0
  73. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/eliminate_subqueries.py +189 -0
  74. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/isolate_table_selects.py +50 -0
  75. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/merge_subqueries.py +415 -0
  76. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/normalize.py +200 -0
  77. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/normalize_identifiers.py +64 -0
  78. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/optimize_joins.py +91 -0
  79. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/optimizer.py +94 -0
  80. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/pushdown_predicates.py +222 -0
  81. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/pushdown_projections.py +172 -0
  82. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/qualify.py +104 -0
  83. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/qualify_columns.py +1024 -0
  84. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/qualify_tables.py +155 -0
  85. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/scope.py +904 -0
  86. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/simplify.py +1587 -0
  87. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/unnest_subqueries.py +302 -0
  88. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/parser.py +8501 -0
  89. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/planner.py +463 -0
  90. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/schema.py +588 -0
  91. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/serde.py +68 -0
  92. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/time.py +687 -0
  93. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/tokens.py +1520 -0
  94. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/transforms.py +1020 -0
  95. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/trie.py +81 -0
  96. package/dbt-tools/dist/altimate_python_packages/dbt_core_integration.py +825 -0
  97. package/dbt-tools/dist/altimate_python_packages/dbt_utils.py +157 -0
  98. package/dbt-tools/dist/index.js +23859 -0
  99. package/package.json +13 -13
  100. package/postinstall.mjs +42 -0
  101. package/skills/altimate-setup/SKILL.md +31 -0
@@ -0,0 +1,203 @@
1
+ from __future__ import annotations
2
+
3
+ import typing as t
4
+
5
+ from sqlglot import exp, parser, tokens
6
+ from sqlglot.dialects.dialect import Dialect
7
+ from sqlglot.helper import seq_get
8
+ from sqlglot.tokens import TokenType
9
+
10
+
11
+ def _select_all(table: exp.Expression) -> t.Optional[exp.Select]:
12
+ return exp.select("*").from_(table, copy=False) if table else None
13
+
14
+
15
+ class PRQL(Dialect):
16
+ DPIPE_IS_STRING_CONCAT = False
17
+
18
+ class Tokenizer(tokens.Tokenizer):
19
+ IDENTIFIERS = ["`"]
20
+ QUOTES = ["'", '"']
21
+
22
+ SINGLE_TOKENS = {
23
+ **tokens.Tokenizer.SINGLE_TOKENS,
24
+ "=": TokenType.ALIAS,
25
+ "'": TokenType.QUOTE,
26
+ '"': TokenType.QUOTE,
27
+ "`": TokenType.IDENTIFIER,
28
+ "#": TokenType.COMMENT,
29
+ }
30
+
31
+ KEYWORDS = {
32
+ **tokens.Tokenizer.KEYWORDS,
33
+ }
34
+
35
+ class Parser(parser.Parser):
36
+ CONJUNCTION = {
37
+ **parser.Parser.CONJUNCTION,
38
+ TokenType.DAMP: exp.And,
39
+ }
40
+
41
+ DISJUNCTION = {
42
+ **parser.Parser.DISJUNCTION,
43
+ TokenType.DPIPE: exp.Or,
44
+ }
45
+
46
+ TRANSFORM_PARSERS = {
47
+ "DERIVE": lambda self, query: self._parse_selection(query),
48
+ "SELECT": lambda self, query: self._parse_selection(query, append=False),
49
+ "TAKE": lambda self, query: self._parse_take(query),
50
+ "FILTER": lambda self, query: query.where(self._parse_disjunction()),
51
+ "APPEND": lambda self, query: query.union(
52
+ _select_all(self._parse_table()), distinct=False, copy=False
53
+ ),
54
+ "REMOVE": lambda self, query: query.except_(
55
+ _select_all(self._parse_table()), distinct=False, copy=False
56
+ ),
57
+ "INTERSECT": lambda self, query: query.intersect(
58
+ _select_all(self._parse_table()), distinct=False, copy=False
59
+ ),
60
+ "SORT": lambda self, query: self._parse_order_by(query),
61
+ "AGGREGATE": lambda self, query: self._parse_selection(
62
+ query, parse_method=self._parse_aggregate, append=False
63
+ ),
64
+ }
65
+
66
+ FUNCTIONS = {
67
+ **parser.Parser.FUNCTIONS,
68
+ "AVERAGE": exp.Avg.from_arg_list,
69
+ "SUM": lambda args: exp.func("COALESCE", exp.Sum(this=seq_get(args, 0)), 0),
70
+ }
71
+
72
+ def _parse_equality(self) -> t.Optional[exp.Expression]:
73
+ eq = self._parse_tokens(self._parse_comparison, self.EQUALITY)
74
+ if not isinstance(eq, (exp.EQ, exp.NEQ)):
75
+ return eq
76
+
77
+ # https://prql-lang.org/book/reference/spec/null.html
78
+ if isinstance(eq.expression, exp.Null):
79
+ is_exp = exp.Is(this=eq.this, expression=eq.expression)
80
+ return is_exp if isinstance(eq, exp.EQ) else exp.Not(this=is_exp)
81
+ if isinstance(eq.this, exp.Null):
82
+ is_exp = exp.Is(this=eq.expression, expression=eq.this)
83
+ return is_exp if isinstance(eq, exp.EQ) else exp.Not(this=is_exp)
84
+ return eq
85
+
86
+ def _parse_statement(self) -> t.Optional[exp.Expression]:
87
+ expression = self._parse_expression()
88
+ expression = expression if expression else self._parse_query()
89
+ return expression
90
+
91
+ def _parse_query(self) -> t.Optional[exp.Query]:
92
+ from_ = self._parse_from()
93
+
94
+ if not from_:
95
+ return None
96
+
97
+ query = exp.select("*").from_(from_, copy=False)
98
+
99
+ while self._match_texts(self.TRANSFORM_PARSERS):
100
+ query = self.TRANSFORM_PARSERS[self._prev.text.upper()](self, query)
101
+
102
+ return query
103
+
104
+ def _parse_selection(
105
+ self,
106
+ query: exp.Query,
107
+ parse_method: t.Optional[t.Callable] = None,
108
+ append: bool = True,
109
+ ) -> exp.Query:
110
+ parse_method = parse_method if parse_method else self._parse_expression
111
+ if self._match(TokenType.L_BRACE):
112
+ selects = self._parse_csv(parse_method)
113
+
114
+ if not self._match(TokenType.R_BRACE, expression=query):
115
+ self.raise_error("Expecting }")
116
+ else:
117
+ expression = parse_method()
118
+ selects = [expression] if expression else []
119
+
120
+ projections = {
121
+ select.alias_or_name: select.this if isinstance(select, exp.Alias) else select
122
+ for select in query.selects
123
+ }
124
+
125
+ selects = [
126
+ select.transform(
127
+ lambda s: (projections[s.name].copy() if s.name in projections else s)
128
+ if isinstance(s, exp.Column)
129
+ else s,
130
+ copy=False,
131
+ )
132
+ for select in selects
133
+ ]
134
+
135
+ return query.select(*selects, append=append, copy=False)
136
+
137
+ def _parse_take(self, query: exp.Query) -> t.Optional[exp.Query]:
138
+ num = self._parse_number() # TODO: TAKE for ranges a..b
139
+ return query.limit(num) if num else None
140
+
141
+ def _parse_ordered(
142
+ self, parse_method: t.Optional[t.Callable] = None
143
+ ) -> t.Optional[exp.Ordered]:
144
+ asc = self._match(TokenType.PLUS)
145
+ desc = self._match(TokenType.DASH) or (asc and False)
146
+ term = term = super()._parse_ordered(parse_method=parse_method)
147
+ if term and desc:
148
+ term.set("desc", True)
149
+ term.set("nulls_first", False)
150
+ return term
151
+
152
+ def _parse_order_by(self, query: exp.Select) -> t.Optional[exp.Query]:
153
+ l_brace = self._match(TokenType.L_BRACE)
154
+ expressions = self._parse_csv(self._parse_ordered)
155
+ if l_brace and not self._match(TokenType.R_BRACE):
156
+ self.raise_error("Expecting }")
157
+ return query.order_by(self.expression(exp.Order, expressions=expressions), copy=False)
158
+
159
+ def _parse_aggregate(self) -> t.Optional[exp.Expression]:
160
+ alias = None
161
+ if self._next and self._next.token_type == TokenType.ALIAS:
162
+ alias = self._parse_id_var(any_token=True)
163
+ self._match(TokenType.ALIAS)
164
+
165
+ name = self._curr and self._curr.text.upper()
166
+ func_builder = self.FUNCTIONS.get(name)
167
+ if func_builder:
168
+ self._advance()
169
+ args = self._parse_column()
170
+ func = func_builder([args])
171
+ else:
172
+ self.raise_error(f"Unsupported aggregation function {name}")
173
+ if alias:
174
+ return self.expression(exp.Alias, this=func, alias=alias)
175
+ return func
176
+
177
+ def _parse_expression(self) -> t.Optional[exp.Expression]:
178
+ if self._next and self._next.token_type == TokenType.ALIAS:
179
+ alias = self._parse_id_var(True)
180
+ self._match(TokenType.ALIAS)
181
+ return self.expression(exp.Alias, this=self._parse_assignment(), alias=alias)
182
+ return self._parse_assignment()
183
+
184
+ def _parse_table(
185
+ self,
186
+ schema: bool = False,
187
+ joins: bool = False,
188
+ alias_tokens: t.Optional[t.Collection[TokenType]] = None,
189
+ parse_bracket: bool = False,
190
+ is_db_reference: bool = False,
191
+ parse_partition: bool = False,
192
+ ) -> t.Optional[exp.Expression]:
193
+ return self._parse_table_parts()
194
+
195
+ def _parse_from(
196
+ self, joins: bool = False, skip_from_token: bool = False
197
+ ) -> t.Optional[exp.From]:
198
+ if not skip_from_token and not self._match(TokenType.FROM):
199
+ return None
200
+
201
+ return self.expression(
202
+ exp.From, comments=self._prev_comments, this=self._parse_table(joins=joins)
203
+ )
@@ -0,0 +1,448 @@
1
+ from __future__ import annotations
2
+
3
+ import typing as t
4
+
5
+ from sqlglot import exp, transforms
6
+ from sqlglot.dialects.dialect import (
7
+ NormalizationStrategy,
8
+ concat_to_dpipe_sql,
9
+ concat_ws_to_dpipe_sql,
10
+ date_delta_sql,
11
+ generatedasidentitycolumnconstraint_sql,
12
+ json_extract_segments,
13
+ no_tablesample_sql,
14
+ rename_func,
15
+ map_date_part,
16
+ )
17
+ from sqlglot.dialects.postgres import Postgres
18
+ from sqlglot.helper import seq_get
19
+ from sqlglot.tokens import TokenType
20
+ from sqlglot.parser import build_convert_timezone
21
+
22
+ if t.TYPE_CHECKING:
23
+ from sqlglot._typing import E
24
+
25
+
26
+ def _build_date_delta(expr_type: t.Type[E]) -> t.Callable[[t.List], E]:
27
+ def _builder(args: t.List) -> E:
28
+ expr = expr_type(
29
+ this=seq_get(args, 2),
30
+ expression=seq_get(args, 1),
31
+ unit=map_date_part(seq_get(args, 0)),
32
+ )
33
+ if expr_type is exp.TsOrDsAdd:
34
+ expr.set("return_type", exp.DataType.build("TIMESTAMP"))
35
+
36
+ return expr
37
+
38
+ return _builder
39
+
40
+
41
+ class Redshift(Postgres):
42
+ # https://docs.aws.amazon.com/redshift/latest/dg/r_names.html
43
+ NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_INSENSITIVE
44
+
45
+ SUPPORTS_USER_DEFINED_TYPES = False
46
+ INDEX_OFFSET = 0
47
+ COPY_PARAMS_ARE_CSV = False
48
+ HEX_LOWERCASE = True
49
+ HAS_DISTINCT_ARRAY_CONSTRUCTORS = True
50
+
51
+ # ref: https://docs.aws.amazon.com/redshift/latest/dg/r_FORMAT_strings.html
52
+ TIME_FORMAT = "'YYYY-MM-DD HH24:MI:SS'"
53
+ TIME_MAPPING = {**Postgres.TIME_MAPPING, "MON": "%b", "HH24": "%H", "HH": "%I"}
54
+
55
+ class Parser(Postgres.Parser):
56
+ FUNCTIONS = {
57
+ **Postgres.Parser.FUNCTIONS,
58
+ "ADD_MONTHS": lambda args: exp.TsOrDsAdd(
59
+ this=seq_get(args, 0),
60
+ expression=seq_get(args, 1),
61
+ unit=exp.var("month"),
62
+ return_type=exp.DataType.build("TIMESTAMP"),
63
+ ),
64
+ "CONVERT_TIMEZONE": lambda args: build_convert_timezone(args, "UTC"),
65
+ "DATEADD": _build_date_delta(exp.TsOrDsAdd),
66
+ "DATE_ADD": _build_date_delta(exp.TsOrDsAdd),
67
+ "DATEDIFF": _build_date_delta(exp.TsOrDsDiff),
68
+ "DATE_DIFF": _build_date_delta(exp.TsOrDsDiff),
69
+ "GETDATE": exp.CurrentTimestamp.from_arg_list,
70
+ "LISTAGG": exp.GroupConcat.from_arg_list,
71
+ "SPLIT_TO_ARRAY": lambda args: exp.StringToArray(
72
+ this=seq_get(args, 0), expression=seq_get(args, 1) or exp.Literal.string(",")
73
+ ),
74
+ "STRTOL": exp.FromBase.from_arg_list,
75
+ }
76
+
77
+ NO_PAREN_FUNCTION_PARSERS = {
78
+ **Postgres.Parser.NO_PAREN_FUNCTION_PARSERS,
79
+ "APPROXIMATE": lambda self: self._parse_approximate_count(),
80
+ "SYSDATE": lambda self: self.expression(exp.CurrentTimestamp, sysdate=True),
81
+ }
82
+
83
+ SUPPORTS_IMPLICIT_UNNEST = True
84
+
85
+ def _parse_table(
86
+ self,
87
+ schema: bool = False,
88
+ joins: bool = False,
89
+ alias_tokens: t.Optional[t.Collection[TokenType]] = None,
90
+ parse_bracket: bool = False,
91
+ is_db_reference: bool = False,
92
+ parse_partition: bool = False,
93
+ ) -> t.Optional[exp.Expression]:
94
+ # Redshift supports UNPIVOTing SUPER objects, e.g. `UNPIVOT foo.obj[0] AS val AT attr`
95
+ unpivot = self._match(TokenType.UNPIVOT)
96
+ table = super()._parse_table(
97
+ schema=schema,
98
+ joins=joins,
99
+ alias_tokens=alias_tokens,
100
+ parse_bracket=parse_bracket,
101
+ is_db_reference=is_db_reference,
102
+ )
103
+
104
+ return self.expression(exp.Pivot, this=table, unpivot=True) if unpivot else table
105
+
106
+ def _parse_convert(
107
+ self, strict: bool, safe: t.Optional[bool] = None
108
+ ) -> t.Optional[exp.Expression]:
109
+ to = self._parse_types()
110
+ self._match(TokenType.COMMA)
111
+ this = self._parse_bitwise()
112
+ return self.expression(exp.TryCast, this=this, to=to, safe=safe)
113
+
114
+ def _parse_approximate_count(self) -> t.Optional[exp.ApproxDistinct]:
115
+ index = self._index - 1
116
+ func = self._parse_function()
117
+
118
+ if isinstance(func, exp.Count) and isinstance(func.this, exp.Distinct):
119
+ return self.expression(exp.ApproxDistinct, this=seq_get(func.this.expressions, 0))
120
+ self._retreat(index)
121
+ return None
122
+
123
+ class Tokenizer(Postgres.Tokenizer):
124
+ BIT_STRINGS = []
125
+ HEX_STRINGS = []
126
+ STRING_ESCAPES = ["\\", "'"]
127
+
128
+ KEYWORDS = {
129
+ **Postgres.Tokenizer.KEYWORDS,
130
+ "(+)": TokenType.JOIN_MARKER,
131
+ "HLLSKETCH": TokenType.HLLSKETCH,
132
+ "MINUS": TokenType.EXCEPT,
133
+ "SUPER": TokenType.SUPER,
134
+ "TOP": TokenType.TOP,
135
+ "UNLOAD": TokenType.COMMAND,
136
+ "VARBYTE": TokenType.VARBINARY,
137
+ "BINARY VARYING": TokenType.VARBINARY,
138
+ }
139
+ KEYWORDS.pop("VALUES")
140
+
141
+ # Redshift allows # to appear as a table identifier prefix
142
+ SINGLE_TOKENS = Postgres.Tokenizer.SINGLE_TOKENS.copy()
143
+ SINGLE_TOKENS.pop("#")
144
+
145
+ class Generator(Postgres.Generator):
146
+ LOCKING_READS_SUPPORTED = False
147
+ QUERY_HINTS = False
148
+ VALUES_AS_TABLE = False
149
+ TZ_TO_WITH_TIME_ZONE = True
150
+ NVL2_SUPPORTED = True
151
+ LAST_DAY_SUPPORTS_DATE_PART = False
152
+ CAN_IMPLEMENT_ARRAY_ANY = False
153
+ MULTI_ARG_DISTINCT = True
154
+ COPY_PARAMS_ARE_WRAPPED = False
155
+ HEX_FUNC = "TO_HEX"
156
+ PARSE_JSON_NAME = "JSON_PARSE"
157
+ ARRAY_CONCAT_IS_VAR_LEN = False
158
+ SUPPORTS_CONVERT_TIMEZONE = True
159
+ EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False
160
+ SUPPORTS_MEDIAN = True
161
+ ALTER_SET_TYPE = "TYPE"
162
+
163
+ # Redshift doesn't have `WITH` as part of their with_properties so we remove it
164
+ WITH_PROPERTIES_PREFIX = " "
165
+
166
+ TYPE_MAPPING = {
167
+ **Postgres.Generator.TYPE_MAPPING,
168
+ exp.DataType.Type.BINARY: "VARBYTE",
169
+ exp.DataType.Type.BLOB: "VARBYTE",
170
+ exp.DataType.Type.INT: "INTEGER",
171
+ exp.DataType.Type.TIMETZ: "TIME",
172
+ exp.DataType.Type.TIMESTAMPTZ: "TIMESTAMP",
173
+ exp.DataType.Type.VARBINARY: "VARBYTE",
174
+ exp.DataType.Type.ROWVERSION: "VARBYTE",
175
+ }
176
+
177
+ TRANSFORMS = {
178
+ **Postgres.Generator.TRANSFORMS,
179
+ exp.ArrayConcat: lambda self, e: self.arrayconcat_sql(e, name="ARRAY_CONCAT"),
180
+ exp.Concat: concat_to_dpipe_sql,
181
+ exp.ConcatWs: concat_ws_to_dpipe_sql,
182
+ exp.ApproxDistinct: lambda self,
183
+ e: f"APPROXIMATE COUNT(DISTINCT {self.sql(e, 'this')})",
184
+ exp.CurrentTimestamp: lambda self, e: (
185
+ "SYSDATE" if e.args.get("sysdate") else "GETDATE()"
186
+ ),
187
+ exp.DateAdd: date_delta_sql("DATEADD"),
188
+ exp.DateDiff: date_delta_sql("DATEDIFF"),
189
+ exp.DistKeyProperty: lambda self, e: self.func("DISTKEY", e.this),
190
+ exp.DistStyleProperty: lambda self, e: self.naked_property(e),
191
+ exp.Explode: lambda self, e: self.explode_sql(e),
192
+ exp.FromBase: rename_func("STRTOL"),
193
+ exp.GeneratedAsIdentityColumnConstraint: generatedasidentitycolumnconstraint_sql,
194
+ exp.JSONExtract: json_extract_segments("JSON_EXTRACT_PATH_TEXT"),
195
+ exp.JSONExtractScalar: json_extract_segments("JSON_EXTRACT_PATH_TEXT"),
196
+ exp.GroupConcat: rename_func("LISTAGG"),
197
+ exp.Hex: lambda self, e: self.func("UPPER", self.func("TO_HEX", self.sql(e, "this"))),
198
+ exp.Select: transforms.preprocess(
199
+ [
200
+ transforms.eliminate_window_clause,
201
+ transforms.eliminate_distinct_on,
202
+ transforms.eliminate_semi_and_anti_joins,
203
+ transforms.unqualify_unnest,
204
+ transforms.unnest_generate_date_array_using_recursive_cte,
205
+ ]
206
+ ),
207
+ exp.SortKeyProperty: lambda self,
208
+ e: f"{'COMPOUND ' if e.args['compound'] else ''}SORTKEY({self.format_args(*e.this)})",
209
+ exp.StartsWith: lambda self,
210
+ e: f"{self.sql(e.this)} LIKE {self.sql(e.expression)} || '%'",
211
+ exp.StringToArray: rename_func("SPLIT_TO_ARRAY"),
212
+ exp.TableSample: no_tablesample_sql,
213
+ exp.TsOrDsAdd: date_delta_sql("DATEADD"),
214
+ exp.TsOrDsDiff: date_delta_sql("DATEDIFF"),
215
+ exp.UnixToTime: lambda self,
216
+ e: f"(TIMESTAMP 'epoch' + {self.sql(e.this)} * INTERVAL '1 SECOND')",
217
+ }
218
+
219
+ # Postgres maps exp.Pivot to no_pivot_sql, but Redshift support pivots
220
+ TRANSFORMS.pop(exp.Pivot)
221
+
222
+ # Postgres doesn't support JSON_PARSE, but Redshift does
223
+ TRANSFORMS.pop(exp.ParseJSON)
224
+
225
+ # Redshift supports these functions
226
+ TRANSFORMS.pop(exp.AnyValue)
227
+ TRANSFORMS.pop(exp.LastDay)
228
+ TRANSFORMS.pop(exp.SHA2)
229
+
230
+ RESERVED_KEYWORDS = {
231
+ "aes128",
232
+ "aes256",
233
+ "all",
234
+ "allowoverwrite",
235
+ "analyse",
236
+ "analyze",
237
+ "and",
238
+ "any",
239
+ "array",
240
+ "as",
241
+ "asc",
242
+ "authorization",
243
+ "az64",
244
+ "backup",
245
+ "between",
246
+ "binary",
247
+ "blanksasnull",
248
+ "both",
249
+ "bytedict",
250
+ "bzip2",
251
+ "case",
252
+ "cast",
253
+ "check",
254
+ "collate",
255
+ "column",
256
+ "constraint",
257
+ "create",
258
+ "credentials",
259
+ "cross",
260
+ "current_date",
261
+ "current_time",
262
+ "current_timestamp",
263
+ "current_user",
264
+ "current_user_id",
265
+ "default",
266
+ "deferrable",
267
+ "deflate",
268
+ "defrag",
269
+ "delta",
270
+ "delta32k",
271
+ "desc",
272
+ "disable",
273
+ "distinct",
274
+ "do",
275
+ "else",
276
+ "emptyasnull",
277
+ "enable",
278
+ "encode",
279
+ "encrypt ",
280
+ "encryption",
281
+ "end",
282
+ "except",
283
+ "explicit",
284
+ "false",
285
+ "for",
286
+ "foreign",
287
+ "freeze",
288
+ "from",
289
+ "full",
290
+ "globaldict256",
291
+ "globaldict64k",
292
+ "grant",
293
+ "group",
294
+ "gzip",
295
+ "having",
296
+ "identity",
297
+ "ignore",
298
+ "ilike",
299
+ "in",
300
+ "initially",
301
+ "inner",
302
+ "intersect",
303
+ "interval",
304
+ "into",
305
+ "is",
306
+ "isnull",
307
+ "join",
308
+ "leading",
309
+ "left",
310
+ "like",
311
+ "limit",
312
+ "localtime",
313
+ "localtimestamp",
314
+ "lun",
315
+ "luns",
316
+ "lzo",
317
+ "lzop",
318
+ "minus",
319
+ "mostly16",
320
+ "mostly32",
321
+ "mostly8",
322
+ "natural",
323
+ "new",
324
+ "not",
325
+ "notnull",
326
+ "null",
327
+ "nulls",
328
+ "off",
329
+ "offline",
330
+ "offset",
331
+ "oid",
332
+ "old",
333
+ "on",
334
+ "only",
335
+ "open",
336
+ "or",
337
+ "order",
338
+ "outer",
339
+ "overlaps",
340
+ "parallel",
341
+ "partition",
342
+ "percent",
343
+ "permissions",
344
+ "pivot",
345
+ "placing",
346
+ "primary",
347
+ "raw",
348
+ "readratio",
349
+ "recover",
350
+ "references",
351
+ "rejectlog",
352
+ "resort",
353
+ "respect",
354
+ "restore",
355
+ "right",
356
+ "select",
357
+ "session_user",
358
+ "similar",
359
+ "snapshot",
360
+ "some",
361
+ "sysdate",
362
+ "system",
363
+ "table",
364
+ "tag",
365
+ "tdes",
366
+ "text255",
367
+ "text32k",
368
+ "then",
369
+ "timestamp",
370
+ "to",
371
+ "top",
372
+ "trailing",
373
+ "true",
374
+ "truncatecolumns",
375
+ "type",
376
+ "union",
377
+ "unique",
378
+ "unnest",
379
+ "unpivot",
380
+ "user",
381
+ "using",
382
+ "verbose",
383
+ "wallet",
384
+ "when",
385
+ "where",
386
+ "with",
387
+ "without",
388
+ }
389
+
390
+ def unnest_sql(self, expression: exp.Unnest) -> str:
391
+ args = expression.expressions
392
+ num_args = len(args)
393
+
394
+ if num_args != 1:
395
+ self.unsupported(f"Unsupported number of arguments in UNNEST: {num_args}")
396
+ return ""
397
+
398
+ if isinstance(expression.find_ancestor(exp.From, exp.Join, exp.Select), exp.Select):
399
+ self.unsupported("Unsupported UNNEST when not used in FROM/JOIN clauses")
400
+ return ""
401
+
402
+ arg = self.sql(seq_get(args, 0))
403
+
404
+ alias = self.expressions(expression.args.get("alias"), key="columns", flat=True)
405
+ return f"{arg} AS {alias}" if alias else arg
406
+
407
+ def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
408
+ if expression.is_type(exp.DataType.Type.JSON):
409
+ # Redshift doesn't support a JSON type, so casting to it is treated as a noop
410
+ return self.sql(expression, "this")
411
+
412
+ return super().cast_sql(expression, safe_prefix=safe_prefix)
413
+
414
+ def datatype_sql(self, expression: exp.DataType) -> str:
415
+ """
416
+ Redshift converts the `TEXT` data type to `VARCHAR(255)` by default when people more generally mean
417
+ VARCHAR of max length which is `VARCHAR(max)` in Redshift. Therefore if we get a `TEXT` data type
418
+ without precision we convert it to `VARCHAR(max)` and if it does have precision then we just convert
419
+ `TEXT` to `VARCHAR`.
420
+ """
421
+ if expression.is_type("text"):
422
+ expression.set("this", exp.DataType.Type.VARCHAR)
423
+ precision = expression.args.get("expressions")
424
+
425
+ if not precision:
426
+ expression.append("expressions", exp.var("MAX"))
427
+
428
+ return super().datatype_sql(expression)
429
+
430
+ def alterset_sql(self, expression: exp.AlterSet) -> str:
431
+ exprs = self.expressions(expression, flat=True)
432
+ exprs = f" TABLE PROPERTIES ({exprs})" if exprs else ""
433
+ location = self.sql(expression, "location")
434
+ location = f" LOCATION {location}" if location else ""
435
+ file_format = self.expressions(expression, key="file_format", flat=True, sep=" ")
436
+ file_format = f" FILE FORMAT {file_format}" if file_format else ""
437
+
438
+ return f"SET{exprs}{location}{file_format}"
439
+
440
+ def array_sql(self, expression: exp.Array) -> str:
441
+ if expression.args.get("bracket_notation"):
442
+ return super().array_sql(expression)
443
+
444
+ return rename_func("ARRAY")(self, expression)
445
+
446
+ def explode_sql(self, expression: exp.Explode) -> str:
447
+ self.unsupported("Unsupported EXPLODE() function")
448
+ return ""