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,101 @@
1
+ from __future__ import annotations
2
+
3
+ import typing as t
4
+
5
+ from sqlglot.executor.env import ENV
6
+
7
+ if t.TYPE_CHECKING:
8
+ from sqlglot.executor.table import Table, TableIter
9
+
10
+
11
+ class Context:
12
+ """
13
+ Execution context for sql expressions.
14
+
15
+ Context is used to hold relevant data tables which can then be queried on with eval.
16
+
17
+ References to columns can either be scalar or vectors. When set_row is used, column references
18
+ evaluate to scalars while set_range evaluates to vectors. This allows convenient and efficient
19
+ evaluation of aggregation functions.
20
+ """
21
+
22
+ def __init__(self, tables: t.Dict[str, Table], env: t.Optional[t.Dict] = None) -> None:
23
+ """
24
+ Args
25
+ tables: representing the scope of the current execution context.
26
+ env: dictionary of functions within the execution context.
27
+ """
28
+ self.tables = tables
29
+ self._table: t.Optional[Table] = None
30
+ self.range_readers = {name: table.range_reader for name, table in self.tables.items()}
31
+ self.row_readers = {name: table.reader for name, table in tables.items()}
32
+ self.env = {**ENV, **(env or {}), "scope": self.row_readers}
33
+
34
+ def eval(self, code):
35
+ return eval(code, self.env)
36
+
37
+ def eval_tuple(self, codes):
38
+ return tuple(self.eval(code) for code in codes)
39
+
40
+ @property
41
+ def table(self) -> Table:
42
+ if self._table is None:
43
+ self._table = list(self.tables.values())[0]
44
+
45
+ for other in self.tables.values():
46
+ if self._table.columns != other.columns:
47
+ raise Exception("Columns are different.")
48
+ if len(self._table.rows) != len(other.rows):
49
+ raise Exception("Rows are different.")
50
+
51
+ return self._table
52
+
53
+ def add_columns(self, *columns: str) -> None:
54
+ for table in self.tables.values():
55
+ table.add_columns(*columns)
56
+
57
+ @property
58
+ def columns(self) -> t.Tuple:
59
+ return self.table.columns
60
+
61
+ def __iter__(self):
62
+ self.env["scope"] = self.row_readers
63
+ for i in range(len(self.table.rows)):
64
+ for table in self.tables.values():
65
+ reader = table[i]
66
+ yield reader, self
67
+
68
+ def table_iter(self, table: str) -> TableIter:
69
+ self.env["scope"] = self.row_readers
70
+ return iter(self.tables[table])
71
+
72
+ def filter(self, condition) -> None:
73
+ rows = [reader.row for reader, _ in self if self.eval(condition)]
74
+
75
+ for table in self.tables.values():
76
+ table.rows = rows
77
+
78
+ def sort(self, key) -> None:
79
+ def sort_key(row: t.Tuple) -> t.Tuple:
80
+ self.set_row(row)
81
+ return tuple((t is None, t) for t in self.eval_tuple(key))
82
+
83
+ self.table.rows.sort(key=sort_key)
84
+
85
+ def set_row(self, row: t.Tuple) -> None:
86
+ for table in self.tables.values():
87
+ table.reader.row = row
88
+ self.env["scope"] = self.row_readers
89
+
90
+ def set_index(self, index: int) -> None:
91
+ for table in self.tables.values():
92
+ table[index]
93
+ self.env["scope"] = self.row_readers
94
+
95
+ def set_range(self, start: int, end: int) -> None:
96
+ for name in self.tables:
97
+ self.range_readers[name].range = range(start, end)
98
+ self.env["scope"] = self.range_readers
99
+
100
+ def __contains__(self, table: str) -> bool:
101
+ return table in self.tables
@@ -0,0 +1,246 @@
1
+ import datetime
2
+ import inspect
3
+ import re
4
+ import statistics
5
+ from functools import wraps
6
+
7
+ from sqlglot import exp
8
+ from sqlglot.generator import Generator
9
+ from sqlglot.helper import PYTHON_VERSION, is_int, seq_get
10
+
11
+
12
+ class reverse_key:
13
+ def __init__(self, obj):
14
+ self.obj = obj
15
+
16
+ def __eq__(self, other):
17
+ return other.obj == self.obj
18
+
19
+ def __lt__(self, other):
20
+ return other.obj < self.obj
21
+
22
+
23
+ def filter_nulls(func, empty_null=True):
24
+ @wraps(func)
25
+ def _func(values):
26
+ filtered = tuple(v for v in values if v is not None)
27
+ if not filtered and empty_null:
28
+ return None
29
+ return func(filtered)
30
+
31
+ return _func
32
+
33
+
34
+ def null_if_any(*required):
35
+ """
36
+ Decorator that makes a function return `None` if any of the `required` arguments are `None`.
37
+
38
+ This also supports decoration with no arguments, e.g.:
39
+
40
+ @null_if_any
41
+ def foo(a, b): ...
42
+
43
+ In which case all arguments are required.
44
+ """
45
+ f = None
46
+ if len(required) == 1 and callable(required[0]):
47
+ f = required[0]
48
+ required = ()
49
+
50
+ def decorator(func):
51
+ if required:
52
+ required_indices = [
53
+ i for i, param in enumerate(inspect.signature(func).parameters) if param in required
54
+ ]
55
+
56
+ def predicate(*args):
57
+ return any(args[i] is None for i in required_indices)
58
+
59
+ else:
60
+
61
+ def predicate(*args):
62
+ return any(a is None for a in args)
63
+
64
+ @wraps(func)
65
+ def _func(*args):
66
+ if predicate(*args):
67
+ return None
68
+ return func(*args)
69
+
70
+ return _func
71
+
72
+ if f:
73
+ return decorator(f)
74
+
75
+ return decorator
76
+
77
+
78
+ @null_if_any("this", "substr")
79
+ def str_position(this, substr, position=None):
80
+ position = position - 1 if position is not None else position
81
+ return this.find(substr, position) + 1
82
+
83
+
84
+ @null_if_any("this")
85
+ def substring(this, start=None, length=None):
86
+ if start is None:
87
+ return this
88
+ elif start == 0:
89
+ return ""
90
+ elif start < 0:
91
+ start = len(this) + start
92
+ else:
93
+ start -= 1
94
+
95
+ end = None if length is None else start + length
96
+
97
+ return this[start:end]
98
+
99
+
100
+ @null_if_any
101
+ def cast(this, to):
102
+ if to == exp.DataType.Type.DATE:
103
+ if isinstance(this, datetime.datetime):
104
+ return this.date()
105
+ if isinstance(this, datetime.date):
106
+ return this
107
+ if isinstance(this, str):
108
+ return datetime.date.fromisoformat(this)
109
+ if to == exp.DataType.Type.TIME:
110
+ if isinstance(this, datetime.datetime):
111
+ return this.time()
112
+ if isinstance(this, datetime.time):
113
+ return this
114
+ if isinstance(this, str):
115
+ return datetime.time.fromisoformat(this)
116
+ if to in (exp.DataType.Type.DATETIME, exp.DataType.Type.TIMESTAMP):
117
+ if isinstance(this, datetime.datetime):
118
+ return this
119
+ if isinstance(this, datetime.date):
120
+ return datetime.datetime(this.year, this.month, this.day)
121
+ if isinstance(this, str):
122
+ return datetime.datetime.fromisoformat(this)
123
+ if to == exp.DataType.Type.BOOLEAN:
124
+ return bool(this)
125
+ if to in exp.DataType.TEXT_TYPES:
126
+ return str(this)
127
+ if to in {exp.DataType.Type.FLOAT, exp.DataType.Type.DOUBLE}:
128
+ return float(this)
129
+ if to in exp.DataType.NUMERIC_TYPES:
130
+ return int(this)
131
+ raise NotImplementedError(f"Casting {this} to '{to}' not implemented.")
132
+
133
+
134
+ def ordered(this, desc, nulls_first):
135
+ if desc:
136
+ return reverse_key(this)
137
+ return this
138
+
139
+
140
+ @null_if_any
141
+ def interval(this, unit):
142
+ plural = unit + "S"
143
+ if plural in Generator.TIME_PART_SINGULARS:
144
+ unit = plural
145
+ return datetime.timedelta(**{unit.lower(): float(this)})
146
+
147
+
148
+ @null_if_any("this", "expression")
149
+ def arraytostring(this, expression, null=None):
150
+ return expression.join(x for x in (x if x is not None else null for x in this) if x is not None)
151
+
152
+
153
+ @null_if_any("this", "expression")
154
+ def jsonextract(this, expression):
155
+ for path_segment in expression:
156
+ if isinstance(this, dict):
157
+ this = this.get(path_segment)
158
+ elif isinstance(this, list) and is_int(path_segment):
159
+ this = seq_get(this, int(path_segment))
160
+ else:
161
+ raise NotImplementedError(f"Unable to extract value for {this} at {path_segment}.")
162
+
163
+ if this is None:
164
+ break
165
+
166
+ return this
167
+
168
+
169
+ ENV = {
170
+ "exp": exp,
171
+ # aggs
172
+ "ARRAYAGG": list,
173
+ "ARRAYUNIQUEAGG": filter_nulls(lambda acc: list(set(acc))),
174
+ "AVG": filter_nulls(statistics.fmean if PYTHON_VERSION >= (3, 8) else statistics.mean), # type: ignore
175
+ "COUNT": filter_nulls(lambda acc: sum(1 for _ in acc), False),
176
+ "MAX": filter_nulls(max),
177
+ "MIN": filter_nulls(min),
178
+ "SUM": filter_nulls(sum),
179
+ # scalar functions
180
+ "ABS": null_if_any(lambda this: abs(this)),
181
+ "ADD": null_if_any(lambda e, this: e + this),
182
+ "ARRAYANY": null_if_any(lambda arr, func: any(func(e) for e in arr)),
183
+ "ARRAYTOSTRING": arraytostring,
184
+ "BETWEEN": null_if_any(lambda this, low, high: low <= this and this <= high),
185
+ "BITWISEAND": null_if_any(lambda this, e: this & e),
186
+ "BITWISELEFTSHIFT": null_if_any(lambda this, e: this << e),
187
+ "BITWISEOR": null_if_any(lambda this, e: this | e),
188
+ "BITWISERIGHTSHIFT": null_if_any(lambda this, e: this >> e),
189
+ "BITWISEXOR": null_if_any(lambda this, e: this ^ e),
190
+ "CAST": cast,
191
+ "COALESCE": lambda *args: next((a for a in args if a is not None), None),
192
+ "CONCAT": null_if_any(lambda *args: "".join(args)),
193
+ "SAFECONCAT": null_if_any(lambda *args: "".join(str(arg) for arg in args)),
194
+ "CONCATWS": null_if_any(lambda this, *args: this.join(args)),
195
+ "DATEDIFF": null_if_any(lambda this, expression, *_: (this - expression).days),
196
+ "DATESTRTODATE": null_if_any(lambda arg: datetime.date.fromisoformat(arg)),
197
+ "DIV": null_if_any(lambda e, this: e / this),
198
+ "DOT": null_if_any(lambda e, this: e[this]),
199
+ "EQ": null_if_any(lambda this, e: this == e),
200
+ "EXTRACT": null_if_any(lambda this, e: getattr(e, this)),
201
+ "GT": null_if_any(lambda this, e: this > e),
202
+ "GTE": null_if_any(lambda this, e: this >= e),
203
+ "IF": lambda predicate, true, false: true if predicate else false,
204
+ "INTDIV": null_if_any(lambda e, this: e // this),
205
+ "INTERVAL": interval,
206
+ "JSONEXTRACT": jsonextract,
207
+ "LEFT": null_if_any(lambda this, e: this[:e]),
208
+ "LIKE": null_if_any(
209
+ lambda this, e: bool(re.match(e.replace("_", ".").replace("%", ".*"), this))
210
+ ),
211
+ "LOWER": null_if_any(lambda arg: arg.lower()),
212
+ "LT": null_if_any(lambda this, e: this < e),
213
+ "LTE": null_if_any(lambda this, e: this <= e),
214
+ "MAP": null_if_any(lambda *args: dict(zip(*args))), # type: ignore
215
+ "MOD": null_if_any(lambda e, this: e % this),
216
+ "MUL": null_if_any(lambda e, this: e * this),
217
+ "NEQ": null_if_any(lambda this, e: this != e),
218
+ "ORD": null_if_any(ord),
219
+ "ORDERED": ordered,
220
+ "POW": pow,
221
+ "RIGHT": null_if_any(lambda this, e: this[-e:]),
222
+ "ROUND": null_if_any(lambda this, decimals=None, truncate=None: round(this, ndigits=decimals)),
223
+ "STRPOSITION": str_position,
224
+ "SUB": null_if_any(lambda e, this: e - this),
225
+ "SUBSTRING": substring,
226
+ "TIMESTRTOTIME": null_if_any(lambda arg: datetime.datetime.fromisoformat(arg)),
227
+ "UPPER": null_if_any(lambda arg: arg.upper()),
228
+ "YEAR": null_if_any(lambda arg: arg.year),
229
+ "MONTH": null_if_any(lambda arg: arg.month),
230
+ "DAY": null_if_any(lambda arg: arg.day),
231
+ "CURRENTDATETIME": datetime.datetime.now,
232
+ "CURRENTTIMESTAMP": datetime.datetime.now,
233
+ "CURRENTTIME": datetime.datetime.now,
234
+ "CURRENTDATE": datetime.date.today,
235
+ "STRFTIME": null_if_any(lambda fmt, arg: datetime.datetime.fromisoformat(arg).strftime(fmt)),
236
+ "STRTOTIME": null_if_any(lambda arg, format: datetime.datetime.strptime(arg, format)),
237
+ "TRIM": null_if_any(lambda this, e=None: this.strip(e)),
238
+ "STRUCT": lambda *args: {
239
+ args[x]: args[x + 1]
240
+ for x in range(0, len(args), 2)
241
+ if (args[x + 1] is not None and args[x] is not None)
242
+ },
243
+ "UNIXTOTIME": null_if_any(
244
+ lambda arg: datetime.datetime.fromtimestamp(arg, datetime.timezone.utc)
245
+ ),
246
+ }