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,320 @@
1
+ from __future__ import annotations
2
+
3
+ import typing as t
4
+
5
+ from sqlglot import exp, generator, parser, tokens, transforms
6
+ from sqlglot.dialects.dialect import (
7
+ Dialect,
8
+ NormalizationStrategy,
9
+ any_value_to_max_sql,
10
+ arrow_json_extract_sql,
11
+ concat_to_dpipe_sql,
12
+ count_if_to_sum,
13
+ no_ilike_sql,
14
+ no_pivot_sql,
15
+ no_tablesample_sql,
16
+ no_trycast_sql,
17
+ rename_func,
18
+ strposition_sql,
19
+ )
20
+ from sqlglot.generator import unsupported_args
21
+ from sqlglot.tokens import TokenType
22
+
23
+
24
+ def _build_strftime(args: t.List) -> exp.Anonymous | exp.TimeToStr:
25
+ if len(args) == 1:
26
+ args.append(exp.CurrentTimestamp())
27
+ if len(args) == 2:
28
+ return exp.TimeToStr(this=exp.TsOrDsToTimestamp(this=args[1]), format=args[0])
29
+ return exp.Anonymous(this="STRFTIME", expressions=args)
30
+
31
+
32
+ def _transform_create(expression: exp.Expression) -> exp.Expression:
33
+ """Move primary key to a column and enforce auto_increment on primary keys."""
34
+ schema = expression.this
35
+
36
+ if isinstance(expression, exp.Create) and isinstance(schema, exp.Schema):
37
+ defs = {}
38
+ primary_key = None
39
+
40
+ for e in schema.expressions:
41
+ if isinstance(e, exp.ColumnDef):
42
+ defs[e.name] = e
43
+ elif isinstance(e, exp.PrimaryKey):
44
+ primary_key = e
45
+
46
+ if primary_key and len(primary_key.expressions) == 1:
47
+ column = defs[primary_key.expressions[0].name]
48
+ column.append(
49
+ "constraints", exp.ColumnConstraint(kind=exp.PrimaryKeyColumnConstraint())
50
+ )
51
+ schema.expressions.remove(primary_key)
52
+ else:
53
+ for column in defs.values():
54
+ auto_increment = None
55
+ for constraint in column.constraints:
56
+ if isinstance(constraint.kind, exp.PrimaryKeyColumnConstraint):
57
+ break
58
+ if isinstance(constraint.kind, exp.AutoIncrementColumnConstraint):
59
+ auto_increment = constraint
60
+ if auto_increment:
61
+ column.constraints.remove(auto_increment)
62
+
63
+ return expression
64
+
65
+
66
+ def _generated_to_auto_increment(expression: exp.Expression) -> exp.Expression:
67
+ if not isinstance(expression, exp.ColumnDef):
68
+ return expression
69
+
70
+ generated = expression.find(exp.GeneratedAsIdentityColumnConstraint)
71
+
72
+ if generated:
73
+ t.cast(exp.ColumnConstraint, generated.parent).pop()
74
+
75
+ not_null = expression.find(exp.NotNullColumnConstraint)
76
+ if not_null:
77
+ t.cast(exp.ColumnConstraint, not_null.parent).pop()
78
+
79
+ expression.append(
80
+ "constraints", exp.ColumnConstraint(kind=exp.AutoIncrementColumnConstraint())
81
+ )
82
+
83
+ return expression
84
+
85
+
86
+ class SQLite(Dialect):
87
+ # https://sqlite.org/forum/forumpost/5e575586ac5c711b?raw
88
+ NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_INSENSITIVE
89
+ SUPPORTS_SEMI_ANTI_JOIN = False
90
+ TYPED_DIVISION = True
91
+ SAFE_DIVISION = True
92
+
93
+ class Tokenizer(tokens.Tokenizer):
94
+ IDENTIFIERS = ['"', ("[", "]"), "`"]
95
+ HEX_STRINGS = [("x'", "'"), ("X'", "'"), ("0x", ""), ("0X", "")]
96
+
97
+ NESTED_COMMENTS = False
98
+
99
+ KEYWORDS = tokens.Tokenizer.KEYWORDS.copy()
100
+ KEYWORDS.pop("/*+")
101
+
102
+ COMMANDS = {*tokens.Tokenizer.COMMANDS, TokenType.REPLACE}
103
+
104
+ class Parser(parser.Parser):
105
+ FUNCTIONS = {
106
+ **parser.Parser.FUNCTIONS,
107
+ "EDITDIST3": exp.Levenshtein.from_arg_list,
108
+ "STRFTIME": _build_strftime,
109
+ "DATETIME": lambda args: exp.Anonymous(this="DATETIME", expressions=args),
110
+ "TIME": lambda args: exp.Anonymous(this="TIME", expressions=args),
111
+ }
112
+
113
+ STRING_ALIASES = True
114
+ ALTER_RENAME_REQUIRES_COLUMN = False
115
+
116
+ def _parse_unique(self) -> exp.UniqueColumnConstraint:
117
+ # Do not consume more tokens if UNIQUE is used as a standalone constraint, e.g:
118
+ # CREATE TABLE foo (bar TEXT UNIQUE REFERENCES baz ...)
119
+ if self._curr.text.upper() in self.CONSTRAINT_PARSERS:
120
+ return self.expression(exp.UniqueColumnConstraint)
121
+
122
+ return super()._parse_unique()
123
+
124
+ class Generator(generator.Generator):
125
+ JOIN_HINTS = False
126
+ TABLE_HINTS = False
127
+ QUERY_HINTS = False
128
+ NVL2_SUPPORTED = False
129
+ JSON_PATH_BRACKETED_KEY_SUPPORTED = False
130
+ SUPPORTS_CREATE_TABLE_LIKE = False
131
+ SUPPORTS_TABLE_ALIAS_COLUMNS = False
132
+ SUPPORTS_TO_NUMBER = False
133
+ SUPPORTS_WINDOW_EXCLUDE = True
134
+ EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False
135
+ SUPPORTS_MEDIAN = False
136
+ JSON_KEY_VALUE_PAIR_SEP = ","
137
+
138
+ SUPPORTED_JSON_PATH_PARTS = {
139
+ exp.JSONPathKey,
140
+ exp.JSONPathRoot,
141
+ exp.JSONPathSubscript,
142
+ }
143
+
144
+ TYPE_MAPPING = {
145
+ **generator.Generator.TYPE_MAPPING,
146
+ exp.DataType.Type.BOOLEAN: "INTEGER",
147
+ exp.DataType.Type.TINYINT: "INTEGER",
148
+ exp.DataType.Type.SMALLINT: "INTEGER",
149
+ exp.DataType.Type.INT: "INTEGER",
150
+ exp.DataType.Type.BIGINT: "INTEGER",
151
+ exp.DataType.Type.FLOAT: "REAL",
152
+ exp.DataType.Type.DOUBLE: "REAL",
153
+ exp.DataType.Type.DECIMAL: "REAL",
154
+ exp.DataType.Type.CHAR: "TEXT",
155
+ exp.DataType.Type.NCHAR: "TEXT",
156
+ exp.DataType.Type.VARCHAR: "TEXT",
157
+ exp.DataType.Type.NVARCHAR: "TEXT",
158
+ exp.DataType.Type.BINARY: "BLOB",
159
+ exp.DataType.Type.VARBINARY: "BLOB",
160
+ }
161
+ TYPE_MAPPING.pop(exp.DataType.Type.BLOB)
162
+
163
+ TOKEN_MAPPING = {
164
+ TokenType.AUTO_INCREMENT: "AUTOINCREMENT",
165
+ }
166
+
167
+ TRANSFORMS = {
168
+ **generator.Generator.TRANSFORMS,
169
+ exp.AnyValue: any_value_to_max_sql,
170
+ exp.Chr: rename_func("CHAR"),
171
+ exp.Concat: concat_to_dpipe_sql,
172
+ exp.CountIf: count_if_to_sum,
173
+ exp.Create: transforms.preprocess([_transform_create]),
174
+ exp.CurrentDate: lambda *_: "CURRENT_DATE",
175
+ exp.CurrentTime: lambda *_: "CURRENT_TIME",
176
+ exp.CurrentTimestamp: lambda *_: "CURRENT_TIMESTAMP",
177
+ exp.ColumnDef: transforms.preprocess([_generated_to_auto_increment]),
178
+ exp.DateStrToDate: lambda self, e: self.sql(e, "this"),
179
+ exp.If: rename_func("IIF"),
180
+ exp.ILike: no_ilike_sql,
181
+ exp.JSONExtractScalar: arrow_json_extract_sql,
182
+ exp.Levenshtein: unsupported_args("ins_cost", "del_cost", "sub_cost", "max_dist")(
183
+ rename_func("EDITDIST3")
184
+ ),
185
+ exp.LogicalOr: rename_func("MAX"),
186
+ exp.LogicalAnd: rename_func("MIN"),
187
+ exp.Pivot: no_pivot_sql,
188
+ exp.Rand: rename_func("RANDOM"),
189
+ exp.Select: transforms.preprocess(
190
+ [
191
+ transforms.eliminate_distinct_on,
192
+ transforms.eliminate_qualify,
193
+ transforms.eliminate_semi_and_anti_joins,
194
+ ]
195
+ ),
196
+ exp.StrPosition: lambda self, e: strposition_sql(self, e, func_name="INSTR"),
197
+ exp.TableSample: no_tablesample_sql,
198
+ exp.TimeStrToTime: lambda self, e: self.sql(e, "this"),
199
+ exp.TimeToStr: lambda self, e: self.func("STRFTIME", e.args.get("format"), e.this),
200
+ exp.TryCast: no_trycast_sql,
201
+ exp.TsOrDsToTimestamp: lambda self, e: self.sql(e, "this"),
202
+ }
203
+
204
+ # SQLite doesn't generally support CREATE TABLE .. properties
205
+ # https://www.sqlite.org/lang_createtable.html
206
+ PROPERTIES_LOCATION = {
207
+ prop: exp.Properties.Location.UNSUPPORTED
208
+ for prop in generator.Generator.PROPERTIES_LOCATION
209
+ }
210
+
211
+ # There are a few exceptions (e.g. temporary tables) which are supported or
212
+ # can be transpiled to SQLite, so we explicitly override them accordingly
213
+ PROPERTIES_LOCATION[exp.LikeProperty] = exp.Properties.Location.POST_SCHEMA
214
+ PROPERTIES_LOCATION[exp.TemporaryProperty] = exp.Properties.Location.POST_CREATE
215
+
216
+ LIMIT_FETCH = "LIMIT"
217
+
218
+ def jsonextract_sql(self, expression: exp.JSONExtract) -> str:
219
+ if expression.expressions:
220
+ return self.function_fallback_sql(expression)
221
+ return arrow_json_extract_sql(self, expression)
222
+
223
+ def dateadd_sql(self, expression: exp.DateAdd) -> str:
224
+ modifier = expression.expression
225
+ modifier = modifier.name if modifier.is_string else self.sql(modifier)
226
+ unit = expression.args.get("unit")
227
+ modifier = f"'{modifier} {unit.name}'" if unit else f"'{modifier}'"
228
+ return self.func("DATE", expression.this, modifier)
229
+
230
+ def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
231
+ if expression.is_type("date"):
232
+ return self.func("DATE", expression.this)
233
+
234
+ return super().cast_sql(expression)
235
+
236
+ def generateseries_sql(self, expression: exp.GenerateSeries) -> str:
237
+ parent = expression.parent
238
+ alias = parent and parent.args.get("alias")
239
+
240
+ if isinstance(alias, exp.TableAlias) and alias.columns:
241
+ column_alias = alias.columns[0]
242
+ alias.set("columns", None)
243
+ sql = self.sql(
244
+ exp.select(exp.alias_("value", column_alias)).from_(expression).subquery()
245
+ )
246
+ else:
247
+ sql = self.function_fallback_sql(expression)
248
+
249
+ return sql
250
+
251
+ def datediff_sql(self, expression: exp.DateDiff) -> str:
252
+ unit = expression.args.get("unit")
253
+ unit = unit.name.upper() if unit else "DAY"
254
+
255
+ sql = f"(JULIANDAY({self.sql(expression, 'this')}) - JULIANDAY({self.sql(expression, 'expression')}))"
256
+
257
+ if unit == "MONTH":
258
+ sql = f"{sql} / 30.0"
259
+ elif unit == "YEAR":
260
+ sql = f"{sql} / 365.0"
261
+ elif unit == "HOUR":
262
+ sql = f"{sql} * 24.0"
263
+ elif unit == "MINUTE":
264
+ sql = f"{sql} * 1440.0"
265
+ elif unit == "SECOND":
266
+ sql = f"{sql} * 86400.0"
267
+ elif unit == "MILLISECOND":
268
+ sql = f"{sql} * 86400000.0"
269
+ elif unit == "MICROSECOND":
270
+ sql = f"{sql} * 86400000000.0"
271
+ elif unit == "NANOSECOND":
272
+ sql = f"{sql} * 8640000000000.0"
273
+ else:
274
+ self.unsupported(f"DATEDIFF unsupported for '{unit}'.")
275
+
276
+ return f"CAST({sql} AS INTEGER)"
277
+
278
+ # https://www.sqlite.org/lang_aggfunc.html#group_concat
279
+ def groupconcat_sql(self, expression: exp.GroupConcat) -> str:
280
+ this = expression.this
281
+ distinct = expression.find(exp.Distinct)
282
+
283
+ if distinct:
284
+ this = distinct.expressions[0]
285
+ distinct_sql = "DISTINCT "
286
+ else:
287
+ distinct_sql = ""
288
+
289
+ if isinstance(expression.this, exp.Order):
290
+ self.unsupported("SQLite GROUP_CONCAT doesn't support ORDER BY.")
291
+ if expression.this.this and not distinct:
292
+ this = expression.this.this
293
+
294
+ separator = expression.args.get("separator")
295
+ return f"GROUP_CONCAT({distinct_sql}{self.format_args(this, separator)})"
296
+
297
+ def least_sql(self, expression: exp.Least) -> str:
298
+ if len(expression.expressions) > 1:
299
+ return rename_func("MIN")(self, expression)
300
+
301
+ return self.sql(expression, "this")
302
+
303
+ def transaction_sql(self, expression: exp.Transaction) -> str:
304
+ this = expression.this
305
+ this = f" {this}" if this else ""
306
+ return f"BEGIN{this} TRANSACTION"
307
+
308
+ def isascii_sql(self, expression: exp.IsAscii) -> str:
309
+ return f"(NOT {self.sql(expression.this)} GLOB CAST(x'2a5b5e012d7f5d2a' AS TEXT))"
310
+
311
+ @unsupported_args("this")
312
+ def currentschema_sql(self, expression: exp.CurrentSchema) -> str:
313
+ return "'main'"
314
+
315
+ def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
316
+ self.unsupported("SQLite does not support IGNORE NULLS.")
317
+ return self.sql(expression.this)
318
+
319
+ def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
320
+ return self.sql(expression.this)
@@ -0,0 +1,343 @@
1
+ from __future__ import annotations
2
+
3
+ import typing as t
4
+
5
+ from sqlglot import exp
6
+ from sqlglot.dialects.dialect import (
7
+ approx_count_distinct_sql,
8
+ arrow_json_extract_sql,
9
+ build_timestamp_trunc,
10
+ rename_func,
11
+ unit_to_str,
12
+ inline_array_sql,
13
+ property_sql,
14
+ )
15
+ from sqlglot.dialects.mysql import MySQL
16
+ from sqlglot.helper import seq_get
17
+ from sqlglot.tokens import TokenType
18
+
19
+
20
+ # https://docs.starrocks.io/docs/sql-reference/sql-functions/spatial-functions/st_distance_sphere/
21
+ def st_distance_sphere(self, expression: exp.StDistance) -> str:
22
+ point1 = expression.this
23
+ point2 = expression.expression
24
+
25
+ point1_x = self.func("ST_X", point1)
26
+ point1_y = self.func("ST_Y", point1)
27
+ point2_x = self.func("ST_X", point2)
28
+ point2_y = self.func("ST_Y", point2)
29
+
30
+ return self.func("ST_Distance_Sphere", point1_x, point1_y, point2_x, point2_y)
31
+
32
+
33
+ class StarRocks(MySQL):
34
+ STRICT_JSON_PATH_SYNTAX = False
35
+
36
+ class Tokenizer(MySQL.Tokenizer):
37
+ KEYWORDS = {
38
+ **MySQL.Tokenizer.KEYWORDS,
39
+ "LARGEINT": TokenType.INT128,
40
+ }
41
+
42
+ class Parser(MySQL.Parser):
43
+ FUNCTIONS = {
44
+ **MySQL.Parser.FUNCTIONS,
45
+ "DATE_TRUNC": build_timestamp_trunc,
46
+ "DATEDIFF": lambda args: exp.DateDiff(
47
+ this=seq_get(args, 0), expression=seq_get(args, 1), unit=exp.Literal.string("DAY")
48
+ ),
49
+ "DATE_DIFF": lambda args: exp.DateDiff(
50
+ this=seq_get(args, 1), expression=seq_get(args, 2), unit=seq_get(args, 0)
51
+ ),
52
+ "REGEXP": exp.RegexpLike.from_arg_list,
53
+ }
54
+
55
+ PROPERTY_PARSERS = {
56
+ **MySQL.Parser.PROPERTY_PARSERS,
57
+ "UNIQUE": lambda self: self._parse_composite_key_property(exp.UniqueKeyProperty),
58
+ "PROPERTIES": lambda self: self._parse_wrapped_properties(),
59
+ "PARTITION BY": lambda self: self._parse_partition_by_opt_range(),
60
+ }
61
+
62
+ def _parse_create(self) -> exp.Create | exp.Command:
63
+ create = super()._parse_create()
64
+
65
+ # Starrocks' primary key is defined outside of the schema, so we need to move it there
66
+ # https://docs.starrocks.io/docs/table_design/table_types/primary_key_table/#usage
67
+ if isinstance(create, exp.Create) and isinstance(create.this, exp.Schema):
68
+ props = create.args.get("properties")
69
+ if props:
70
+ primary_key = props.find(exp.PrimaryKey)
71
+ if primary_key:
72
+ create.this.append("expressions", primary_key.pop())
73
+
74
+ return create
75
+
76
+ def _parse_unnest(self, with_alias: bool = True) -> t.Optional[exp.Unnest]:
77
+ unnest = super()._parse_unnest(with_alias=with_alias)
78
+
79
+ if unnest:
80
+ alias = unnest.args.get("alias")
81
+
82
+ if not alias:
83
+ # Starrocks defaults to naming the table alias as "unnest"
84
+ alias = exp.TableAlias(
85
+ this=exp.to_identifier("unnest"), columns=[exp.to_identifier("unnest")]
86
+ )
87
+ unnest.set("alias", alias)
88
+ elif not alias.args.get("columns"):
89
+ # Starrocks defaults to naming the UNNEST column as "unnest"
90
+ # if it's not otherwise specified
91
+ alias.set("columns", [exp.to_identifier("unnest")])
92
+
93
+ return unnest
94
+
95
+ def _parse_partitioning_granularity_dynamic(self) -> exp.PartitionByRangePropertyDynamic:
96
+ self._match_text_seq("START")
97
+ start = self._parse_wrapped(self._parse_string)
98
+ self._match_text_seq("END")
99
+ end = self._parse_wrapped(self._parse_string)
100
+ self._match_text_seq("EVERY")
101
+ every = self._parse_wrapped(lambda: self._parse_interval() or self._parse_number())
102
+ return self.expression(
103
+ exp.PartitionByRangePropertyDynamic, start=start, end=end, every=every
104
+ )
105
+
106
+ def _parse_partition_by_opt_range(
107
+ self,
108
+ ) -> exp.PartitionedByProperty | exp.PartitionByRangeProperty:
109
+ if self._match_text_seq("RANGE"):
110
+ partition_expressions = self._parse_wrapped_id_vars()
111
+ create_expressions = self._parse_wrapped_csv(
112
+ self._parse_partitioning_granularity_dynamic
113
+ )
114
+ return self.expression(
115
+ exp.PartitionByRangeProperty,
116
+ partition_expressions=partition_expressions,
117
+ create_expressions=create_expressions,
118
+ )
119
+ return super()._parse_partitioned_by()
120
+
121
+ class Generator(MySQL.Generator):
122
+ EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False
123
+ JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
124
+ VARCHAR_REQUIRES_SIZE = False
125
+ PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON"
126
+ WITH_PROPERTIES_PREFIX = "PROPERTIES"
127
+
128
+ CAST_MAPPING = {}
129
+
130
+ TYPE_MAPPING = {
131
+ **MySQL.Generator.TYPE_MAPPING,
132
+ exp.DataType.Type.INT128: "LARGEINT",
133
+ exp.DataType.Type.TEXT: "STRING",
134
+ exp.DataType.Type.TIMESTAMP: "DATETIME",
135
+ exp.DataType.Type.TIMESTAMPTZ: "DATETIME",
136
+ }
137
+
138
+ PROPERTIES_LOCATION = {
139
+ **MySQL.Generator.PROPERTIES_LOCATION,
140
+ exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
141
+ exp.UniqueKeyProperty: exp.Properties.Location.POST_SCHEMA,
142
+ exp.PartitionByRangeProperty: exp.Properties.Location.POST_SCHEMA,
143
+ }
144
+
145
+ TRANSFORMS = {
146
+ **MySQL.Generator.TRANSFORMS,
147
+ exp.Array: inline_array_sql,
148
+ exp.ArrayAgg: rename_func("ARRAY_AGG"),
149
+ exp.ArrayFilter: rename_func("ARRAY_FILTER"),
150
+ exp.ArrayToString: rename_func("ARRAY_JOIN"),
151
+ exp.ApproxDistinct: approx_count_distinct_sql,
152
+ exp.DateDiff: lambda self, e: self.func(
153
+ "DATE_DIFF", unit_to_str(e), e.this, e.expression
154
+ ),
155
+ exp.JSONExtractScalar: arrow_json_extract_sql,
156
+ exp.JSONExtract: arrow_json_extract_sql,
157
+ exp.Property: property_sql,
158
+ exp.RegexpLike: rename_func("REGEXP"),
159
+ exp.StDistance: st_distance_sphere,
160
+ exp.StrToUnix: lambda self, e: self.func("UNIX_TIMESTAMP", e.this, self.format_time(e)),
161
+ exp.TimestampTrunc: lambda self, e: self.func("DATE_TRUNC", unit_to_str(e), e.this),
162
+ exp.TimeStrToDate: rename_func("TO_DATE"),
163
+ exp.UnixToStr: lambda self, e: self.func("FROM_UNIXTIME", e.this, self.format_time(e)),
164
+ exp.UnixToTime: rename_func("FROM_UNIXTIME"),
165
+ }
166
+
167
+ TRANSFORMS.pop(exp.DateTrunc)
168
+
169
+ # https://docs.starrocks.io/docs/sql-reference/sql-statements/keywords/#reserved-keywords
170
+ RESERVED_KEYWORDS = {
171
+ "add",
172
+ "all",
173
+ "alter",
174
+ "analyze",
175
+ "and",
176
+ "array",
177
+ "as",
178
+ "asc",
179
+ "between",
180
+ "bigint",
181
+ "bitmap",
182
+ "both",
183
+ "by",
184
+ "case",
185
+ "char",
186
+ "character",
187
+ "check",
188
+ "collate",
189
+ "column",
190
+ "compaction",
191
+ "convert",
192
+ "create",
193
+ "cross",
194
+ "cube",
195
+ "current_date",
196
+ "current_role",
197
+ "current_time",
198
+ "current_timestamp",
199
+ "current_user",
200
+ "database",
201
+ "databases",
202
+ "decimal",
203
+ "decimalv2",
204
+ "decimal32",
205
+ "decimal64",
206
+ "decimal128",
207
+ "default",
208
+ "deferred",
209
+ "delete",
210
+ "dense_rank",
211
+ "desc",
212
+ "describe",
213
+ "distinct",
214
+ "double",
215
+ "drop",
216
+ "dual",
217
+ "else",
218
+ "except",
219
+ "exists",
220
+ "explain",
221
+ "false",
222
+ "first_value",
223
+ "float",
224
+ "for",
225
+ "force",
226
+ "from",
227
+ "full",
228
+ "function",
229
+ "grant",
230
+ "group",
231
+ "grouping",
232
+ "grouping_id",
233
+ "groups",
234
+ "having",
235
+ "hll",
236
+ "host",
237
+ "if",
238
+ "ignore",
239
+ "immediate",
240
+ "in",
241
+ "index",
242
+ "infile",
243
+ "inner",
244
+ "insert",
245
+ "int",
246
+ "integer",
247
+ "intersect",
248
+ "into",
249
+ "is",
250
+ "join",
251
+ "json",
252
+ "key",
253
+ "keys",
254
+ "kill",
255
+ "lag",
256
+ "largeint",
257
+ "last_value",
258
+ "lateral",
259
+ "lead",
260
+ "left",
261
+ "like",
262
+ "limit",
263
+ "load",
264
+ "localtime",
265
+ "localtimestamp",
266
+ "maxvalue",
267
+ "minus",
268
+ "mod",
269
+ "not",
270
+ "ntile",
271
+ "null",
272
+ "on",
273
+ "or",
274
+ "order",
275
+ "outer",
276
+ "outfile",
277
+ "over",
278
+ "partition",
279
+ "percentile",
280
+ "primary",
281
+ "procedure",
282
+ "qualify",
283
+ "range",
284
+ "rank",
285
+ "read",
286
+ "regexp",
287
+ "release",
288
+ "rename",
289
+ "replace",
290
+ "revoke",
291
+ "right",
292
+ "rlike",
293
+ "row",
294
+ "row_number",
295
+ "rows",
296
+ "schema",
297
+ "schemas",
298
+ "select",
299
+ "set",
300
+ "set_var",
301
+ "show",
302
+ "smallint",
303
+ "system",
304
+ "table",
305
+ "terminated",
306
+ "text",
307
+ "then",
308
+ "tinyint",
309
+ "to",
310
+ "true",
311
+ "union",
312
+ "unique",
313
+ "unsigned",
314
+ "update",
315
+ "use",
316
+ "using",
317
+ "values",
318
+ "varchar",
319
+ "when",
320
+ "where",
321
+ "with",
322
+ }
323
+
324
+ def create_sql(self, expression: exp.Create) -> str:
325
+ # Starrocks' primary key is defined outside of the schema, so we need to move it there
326
+ schema = expression.this
327
+ if isinstance(schema, exp.Schema):
328
+ primary_key = schema.find(exp.PrimaryKey)
329
+
330
+ if primary_key:
331
+ props = expression.args.get("properties")
332
+
333
+ if not props:
334
+ props = exp.Properties(expressions=[])
335
+ expression.set("properties", props)
336
+
337
+ # Verify if the first one is an engine property. Is true then insert it after the engine,
338
+ # otherwise insert it at the beginning
339
+ engine = props.find(exp.EngineProperty)
340
+ engine_index = (engine.index or 0) if engine else -1
341
+ props.set("expressions", primary_key.pop(), engine_index + 1, overwrite=False)
342
+
343
+ return super().create_sql(expression)