relationalai 0.11.2__py3-none-any.whl → 0.11.4__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.
- relationalai/clients/snowflake.py +44 -15
- relationalai/clients/types.py +1 -0
- relationalai/clients/use_index_poller.py +446 -178
- relationalai/early_access/builder/std/__init__.py +1 -1
- relationalai/early_access/dsl/bindings/csv.py +4 -4
- relationalai/semantics/internal/internal.py +22 -4
- relationalai/semantics/lqp/executor.py +69 -18
- relationalai/semantics/lqp/intrinsics.py +23 -0
- relationalai/semantics/lqp/model2lqp.py +16 -6
- relationalai/semantics/lqp/passes.py +3 -4
- relationalai/semantics/lqp/primitives.py +38 -14
- relationalai/semantics/metamodel/builtins.py +152 -11
- relationalai/semantics/metamodel/factory.py +3 -2
- relationalai/semantics/metamodel/helpers.py +78 -2
- relationalai/semantics/reasoners/graph/core.py +343 -40
- relationalai/semantics/reasoners/optimization/solvers_dev.py +20 -1
- relationalai/semantics/reasoners/optimization/solvers_pb.py +24 -3
- relationalai/semantics/rel/compiler.py +5 -17
- relationalai/semantics/rel/executor.py +2 -2
- relationalai/semantics/rel/rel.py +6 -0
- relationalai/semantics/rel/rel_utils.py +37 -1
- relationalai/semantics/rel/rewrite/extract_common.py +153 -242
- relationalai/semantics/sql/compiler.py +540 -202
- relationalai/semantics/sql/executor/duck_db.py +21 -0
- relationalai/semantics/sql/executor/result_helpers.py +7 -0
- relationalai/semantics/sql/executor/snowflake.py +9 -2
- relationalai/semantics/sql/rewrite/denormalize.py +4 -6
- relationalai/semantics/sql/rewrite/recursive_union.py +23 -3
- relationalai/semantics/sql/sql.py +120 -46
- relationalai/semantics/std/__init__.py +9 -4
- relationalai/semantics/std/datetime.py +363 -0
- relationalai/semantics/std/math.py +77 -0
- relationalai/semantics/std/re.py +83 -0
- relationalai/semantics/std/strings.py +1 -1
- relationalai/tools/cli_controls.py +445 -60
- relationalai/util/format.py +78 -1
- {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/METADATA +3 -2
- {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/RECORD +41 -39
- relationalai/semantics/std/dates.py +0 -213
- {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/WHEEL +0 -0
- {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/entry_points.txt +0 -0
- {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import duckdb
|
|
4
|
+
import math
|
|
4
5
|
from pandas import DataFrame
|
|
5
6
|
from typing import Any, Union, Literal
|
|
7
|
+
from scipy.special import erfinv as special_erfinv
|
|
6
8
|
|
|
7
9
|
from relationalai.semantics.sql import Compiler
|
|
8
10
|
from relationalai.semantics.sql.executor.result_helpers import format_duckdb_columns
|
|
@@ -18,10 +20,29 @@ class DuckDBExecutor(e.Executor):
|
|
|
18
20
|
""" Execute the SQL query directly. """
|
|
19
21
|
if format != "pandas":
|
|
20
22
|
raise ValueError(f"Unsupported format: {format}")
|
|
23
|
+
|
|
21
24
|
connection = duckdb.connect()
|
|
25
|
+
|
|
26
|
+
# Register scalar functions
|
|
27
|
+
connection.create_function("erf", self.erf)
|
|
28
|
+
connection.create_function("acot", self.acot)
|
|
29
|
+
connection.create_function("erfinv", self.erfinv)
|
|
30
|
+
|
|
22
31
|
try:
|
|
23
32
|
sql, _ = self.compiler.compile(model, {"is_duck_db": True})
|
|
24
33
|
arrow_table = connection.query(sql).fetch_arrow_table()
|
|
25
34
|
return format_duckdb_columns(arrow_table.to_pandas(), arrow_table.schema)
|
|
26
35
|
finally:
|
|
27
36
|
connection.close()
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def erf(x: float) -> float:
|
|
40
|
+
return math.erf(x)
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def erfinv(x: float) -> float:
|
|
44
|
+
return special_erfinv(x)
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def acot(x: float) -> float:
|
|
48
|
+
return math.atan(1 / x) if x != 0 else math.copysign(math.pi / 2, x)
|
|
@@ -35,7 +35,14 @@ def format_columns(result_frame:pd.DataFrame, result_metadata:Sequence[ResultMet
|
|
|
35
35
|
result_frame[col] = series.astype(Int128Dtype())
|
|
36
36
|
elif col_metadata.precision:
|
|
37
37
|
result_frame[col] = _cast_integer_column(series, col_metadata.precision)
|
|
38
|
+
# Handle Snowflake VARIANT columns that are actually numeric
|
|
39
|
+
elif FIELD_ID_TO_NAME[col_metadata.type_code] == "VARIANT":
|
|
40
|
+
series = result_frame[col]
|
|
38
41
|
|
|
42
|
+
if col_type == Int64:
|
|
43
|
+
result_frame[col] = series.astype("Int64")
|
|
44
|
+
elif col_type == Int128:
|
|
45
|
+
result_frame[col] = series.astype(Int128Dtype())
|
|
39
46
|
|
|
40
47
|
# SQL may return None for nulls; replace with np.nan unless the column is FIXED, because
|
|
41
48
|
# both Int64 and Int128 already deal with nulls using pd.NA
|
|
@@ -65,9 +65,15 @@ class SnowflakeExecutor(e.Executor):
|
|
|
65
65
|
update: bool = False) -> Union[pd.DataFrame, Any]:
|
|
66
66
|
""" Execute the SQL query directly. """
|
|
67
67
|
|
|
68
|
+
warehouse = self.resources.config.get("warehouse", None)
|
|
69
|
+
default_dynamic_table_target_lag = (
|
|
70
|
+
self.resources.config.get("reasoner.rule.sql.default_dynamic_table_target_lag", None))
|
|
71
|
+
|
|
72
|
+
options = {"warehouse": warehouse, "default_dynamic_table_target_lag": default_dynamic_table_target_lag}
|
|
73
|
+
|
|
68
74
|
if self._last_model != model:
|
|
69
75
|
with debugging.span("compile", metamodel=model) as model_span:
|
|
70
|
-
model_sql, _ = self.compiler.compile(model)
|
|
76
|
+
model_sql, _ = self.compiler.compile(model, options)
|
|
71
77
|
model_span["compile_type"] = "model"
|
|
72
78
|
model_span["sql"] = model_sql
|
|
73
79
|
self._last_model = model
|
|
@@ -77,7 +83,8 @@ class SnowflakeExecutor(e.Executor):
|
|
|
77
83
|
# compile into sql and keep the new_task, which is the task model after rewrites,
|
|
78
84
|
# as it may contain results of type inference, which is useful for determining
|
|
79
85
|
# how to format the outputs
|
|
80
|
-
|
|
86
|
+
query_options = {**options, "query_compilation": True}
|
|
87
|
+
query_sql, new_task = self.compiler.compile(f.compute_model(f.logical([task])), query_options)
|
|
81
88
|
compile_span["compile_type"] = "query"
|
|
82
89
|
compile_span["sql"] = query_sql
|
|
83
90
|
|
|
@@ -25,7 +25,6 @@ class Denormalize(c.Pass):
|
|
|
25
25
|
|
|
26
26
|
@dataclass
|
|
27
27
|
class OldDenormalize(visitor.Rewriter):
|
|
28
|
-
# TODO: use the new Rewriter when available.
|
|
29
28
|
|
|
30
29
|
denormalized: dict[ir.Relation, ir.Relation] = field(default_factory=dict, init=False, hash=False, compare=False)
|
|
31
30
|
|
|
@@ -168,13 +167,12 @@ class OldDenormalize(visitor.Rewriter):
|
|
|
168
167
|
""" Denormalize the relations that can be denormalized.
|
|
169
168
|
|
|
170
169
|
Group together relations that are keyed by the same "entity". This method defines
|
|
171
|
-
entities as being types that
|
|
170
|
+
entities as being types that have a unary relation containing only that type. All
|
|
172
171
|
relations whose first argument is this type are grouped together.
|
|
173
172
|
|
|
174
173
|
Returns a tuple with 2 elements:
|
|
175
|
-
1.
|
|
176
|
-
2.
|
|
177
|
-
its place.
|
|
174
|
+
1. The new set of relations after denormalization
|
|
175
|
+
2. A dict from relations that were denormalized away to the new relation that took its place.
|
|
178
176
|
"""
|
|
179
177
|
new_relations = ordered_set()
|
|
180
178
|
denormalized: dict[ir.Relation, ir.Relation] = dict()
|
|
@@ -184,7 +182,7 @@ class OldDenormalize(visitor.Rewriter):
|
|
|
184
182
|
entity_relations: dict[ir.Type, ir.Relation] = dict()
|
|
185
183
|
|
|
186
184
|
for r in relations:
|
|
187
|
-
if len(r.fields) == 1 and not types.is_builtin(r.fields[0].type):
|
|
185
|
+
if len(r.fields) == 1 and not types.is_builtin(r.fields[0].type) and not r.name == 'Error':
|
|
188
186
|
e = r.fields[0].type
|
|
189
187
|
entity_types.add(e)
|
|
190
188
|
entity_relations[e] = r
|
|
@@ -49,9 +49,29 @@ class RecursiveUnion(c.Pass):
|
|
|
49
49
|
new_body = [logical for logical in root_logical.body if logical not in recursive_logicals]
|
|
50
50
|
|
|
51
51
|
# Step 4: Add unions for each recursive group
|
|
52
|
-
for
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
for rel_id, logical_group in recursive_groups.items():
|
|
53
|
+
split_group = ordered_set()
|
|
54
|
+
|
|
55
|
+
for logical in logical_group:
|
|
56
|
+
# Count total ir.Update tasks in this logical
|
|
57
|
+
update_count = sum(isinstance(t, ir.Update) for t in logical.body)
|
|
58
|
+
|
|
59
|
+
# If there's only one, keep the original logical as-is
|
|
60
|
+
if update_count == 1:
|
|
61
|
+
split_group.add(logical)
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
# Otherwise, keep only updates relevant to this relation (and non-update tasks)
|
|
65
|
+
filtered_body = [
|
|
66
|
+
t for t in logical.body
|
|
67
|
+
if not isinstance(t, ir.Update) or t.relation.id == rel_id
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
if filtered_body:
|
|
71
|
+
split_group.add(f.logical(filtered_body))
|
|
72
|
+
|
|
73
|
+
if split_group:
|
|
74
|
+
new_body.append(f.union(list(split_group)))
|
|
55
75
|
|
|
56
76
|
return model.reconstruct(model.engines, model.relations, model.types, f.logical(new_body), model.annotations)
|
|
57
77
|
|
|
@@ -6,7 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
import datetime
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from io import StringIO
|
|
9
|
-
from typing import Optional, Union, Tuple, Any
|
|
9
|
+
from typing import Optional, Union, Tuple, Any, Sequence
|
|
10
10
|
from relationalai.semantics.metamodel.util import Printer as BasePrinter
|
|
11
11
|
import json
|
|
12
12
|
|
|
@@ -43,17 +43,35 @@ class CreateTable(Node):
|
|
|
43
43
|
table: Table
|
|
44
44
|
if_not_exists: bool = False
|
|
45
45
|
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class CreateDynamicTable(Node):
|
|
48
|
+
name: str
|
|
49
|
+
query: Union[list[Select], CTE]
|
|
50
|
+
target_lag: str
|
|
51
|
+
warehouse: str
|
|
52
|
+
|
|
46
53
|
@dataclass(frozen=True)
|
|
47
54
|
class CreateView(Node):
|
|
48
55
|
name: str
|
|
49
|
-
query: Union[Select, CTE]
|
|
56
|
+
query: Union[list[Select], CTE]
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True)
|
|
59
|
+
class CreateFunction(Node):
|
|
60
|
+
name: str
|
|
61
|
+
inputs: list[Column]
|
|
62
|
+
return_type: str
|
|
63
|
+
body: str
|
|
64
|
+
language: str = "PYTHON"
|
|
65
|
+
runtime_version: str = "3.11"
|
|
66
|
+
handler: str = "compute"
|
|
67
|
+
packages: Optional[list[str]] = None
|
|
50
68
|
|
|
51
69
|
@dataclass(frozen=True)
|
|
52
70
|
class Insert(Node):
|
|
53
71
|
table: str
|
|
54
72
|
columns: list[str]
|
|
55
73
|
values: list[Tuple[Any, ...]]
|
|
56
|
-
|
|
74
|
+
query: Optional[Union[Select, CTE]] = None
|
|
57
75
|
|
|
58
76
|
@dataclass(frozen=True)
|
|
59
77
|
class Update(Node):
|
|
@@ -69,11 +87,12 @@ class UpdateSet(Node):
|
|
|
69
87
|
@dataclass(frozen=True)
|
|
70
88
|
class Select(Node):
|
|
71
89
|
distinct: bool
|
|
72
|
-
vars:
|
|
73
|
-
froms: Union[list[From], Select]
|
|
90
|
+
vars: Optional[Sequence[Union[VarRef, RowNumberVar, int]]] = None
|
|
91
|
+
froms: Optional[Union[list[From], Select]] = None
|
|
74
92
|
where: Optional[Where] = None
|
|
75
93
|
joins: Optional[list[Join]] = None
|
|
76
94
|
group_by: Optional[list[VarRef]] = None
|
|
95
|
+
order_by: Optional[list[VarRef]] = None
|
|
77
96
|
limit: Optional[int] = None
|
|
78
97
|
is_output: bool = False
|
|
79
98
|
|
|
@@ -86,11 +105,12 @@ class VarRef(Node):
|
|
|
86
105
|
name: str
|
|
87
106
|
column: Optional[str] = None
|
|
88
107
|
alias: Optional[str] = None
|
|
108
|
+
type: Optional[str] = None
|
|
89
109
|
|
|
90
110
|
@dataclass(frozen=True)
|
|
91
111
|
class From(Node):
|
|
92
112
|
table: str
|
|
93
|
-
alias: Optional[str]
|
|
113
|
+
alias: Optional[str] = None
|
|
94
114
|
|
|
95
115
|
@dataclass(frozen=True)
|
|
96
116
|
class Join(Node):
|
|
@@ -106,11 +126,16 @@ class LeftOuterJoin(Join):
|
|
|
106
126
|
class FullOuterJoin(Join):
|
|
107
127
|
pass
|
|
108
128
|
|
|
129
|
+
@dataclass(frozen=True)
|
|
130
|
+
class JoinWithoutCondition(Join):
|
|
131
|
+
pass
|
|
132
|
+
|
|
109
133
|
@dataclass(frozen=True)
|
|
110
134
|
class RowNumberVar(Node):
|
|
111
135
|
order_by_vars: list[OrderByVar]
|
|
112
136
|
partition_by_vars: list[str]
|
|
113
137
|
alias: str
|
|
138
|
+
type: Optional[str] = None
|
|
114
139
|
|
|
115
140
|
@dataclass(frozen=True)
|
|
116
141
|
class OrderByVar(Node):
|
|
@@ -120,7 +145,7 @@ class OrderByVar(Node):
|
|
|
120
145
|
# TODO: consider removing Where and make Select.where: Expr
|
|
121
146
|
@dataclass(frozen=True)
|
|
122
147
|
class Where(Node):
|
|
123
|
-
expression: Expr
|
|
148
|
+
expression: Optional[Expr] = None
|
|
124
149
|
|
|
125
150
|
@dataclass(frozen=True)
|
|
126
151
|
class RawSource(Node):
|
|
@@ -139,6 +164,7 @@ class CTE(Node):
|
|
|
139
164
|
name: str
|
|
140
165
|
columns: list[str]
|
|
141
166
|
selects: list[Select]
|
|
167
|
+
distinct: bool = False
|
|
142
168
|
|
|
143
169
|
#-------------------------------------------------
|
|
144
170
|
# Expressions
|
|
@@ -216,10 +242,9 @@ class Printer(BasePrinter):
|
|
|
216
242
|
self._print(", ")
|
|
217
243
|
self._print_value(v, True)
|
|
218
244
|
elif isinstance(value, str):
|
|
219
|
-
if
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
self._print(value)
|
|
245
|
+
value = 'NULL' if value.lower() == 'none' else value
|
|
246
|
+
value = json.dumps(value) if quote_strings else value
|
|
247
|
+
self._print(value)
|
|
223
248
|
elif isinstance(value, bool):
|
|
224
249
|
self._print(str(value).lower())
|
|
225
250
|
elif isinstance(value, datetime.date):
|
|
@@ -229,6 +254,18 @@ class Printer(BasePrinter):
|
|
|
229
254
|
else:
|
|
230
255
|
self._print(str(value))
|
|
231
256
|
|
|
257
|
+
def _print_query(self, indent, node, union_type:str = "UNION ALL"):
|
|
258
|
+
queries = node.query if isinstance(node.query, list) else [node.query]
|
|
259
|
+
if len(queries) == 1:
|
|
260
|
+
self.print_node(queries[0], indent, True)
|
|
261
|
+
else:
|
|
262
|
+
for i, s in enumerate(queries):
|
|
263
|
+
self._nl()
|
|
264
|
+
if i != 0:
|
|
265
|
+
self._indent_print_nl(indent + 2, union_type)
|
|
266
|
+
self.print_node(s, indent + 1, True)
|
|
267
|
+
self._print(";")
|
|
268
|
+
|
|
232
269
|
def _get_table_name(self, name: str) -> str:
|
|
233
270
|
return f'"{name}"' if self._is_reserved_name(name) else name
|
|
234
271
|
|
|
@@ -251,12 +288,31 @@ class Printer(BasePrinter):
|
|
|
251
288
|
elif isinstance(node, CreateTable):
|
|
252
289
|
clause = "IF NOT EXISTS " if node.if_not_exists else ""
|
|
253
290
|
self._print(f"CREATE TABLE {clause}{node.table};")
|
|
291
|
+
elif isinstance(node, CreateDynamicTable):
|
|
292
|
+
self._print_nl(f"CREATE DYNAMIC TABLE {self._get_table_name(node.name)}")
|
|
293
|
+
self._indent_print_nl(indent+1, f"TARGET_LAG = '{node.target_lag}'")
|
|
294
|
+
self._indent_print_nl(indent+1, f"WAREHOUSE = '{node.warehouse}'")
|
|
295
|
+
self._print("AS ")
|
|
296
|
+
self._print_query(indent, node)
|
|
254
297
|
elif isinstance(node, CreateView):
|
|
255
|
-
# TODO - crying a bit inside :(
|
|
256
|
-
self._print_nl(f"DROP TABLE IF EXISTS {self._get_table_name(node.name)};")
|
|
257
298
|
self._print(f"CREATE VIEW {self._get_table_name(node.name)} AS ")
|
|
258
|
-
self.
|
|
259
|
-
|
|
299
|
+
self._print_query(indent, node, "UNION")
|
|
300
|
+
elif isinstance(node, CreateFunction):
|
|
301
|
+
self._print(f"CREATE OR REPLACE FUNCTION {self._get_table_name(node.name)} (")
|
|
302
|
+
self._join(node.inputs)
|
|
303
|
+
self._print_nl(")")
|
|
304
|
+
self._print_nl(f"RETURNS {node.return_type}")
|
|
305
|
+
self._print_nl(f"LANGUAGE {node.language}")
|
|
306
|
+
self._print_nl(f"RUNTIME_VERSION = '{node.runtime_version}'")
|
|
307
|
+
self._print_nl(f"HANDLER = '{node.handler}'")
|
|
308
|
+
if node.packages:
|
|
309
|
+
self._print("PACKAGES = (")
|
|
310
|
+
self._join(node.packages)
|
|
311
|
+
self._print_nl(")")
|
|
312
|
+
self._print_nl("AS ")
|
|
313
|
+
self._print_nl("$$")
|
|
314
|
+
self._print_nl(node.body)
|
|
315
|
+
self._print("$$;")
|
|
260
316
|
elif isinstance(node, Insert):
|
|
261
317
|
self._print(f"INSERT INTO {self._get_table_name(node.table)} ")
|
|
262
318
|
if len(node.columns) > 0:
|
|
@@ -271,9 +327,9 @@ class Printer(BasePrinter):
|
|
|
271
327
|
self._print(" UNION ALL ")
|
|
272
328
|
self._print("SELECT ")
|
|
273
329
|
self._join(value)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
330
|
+
self._print(";")
|
|
331
|
+
if node.query is not None:
|
|
332
|
+
self._print_query(indent, node)
|
|
277
333
|
elif isinstance(node, Update):
|
|
278
334
|
self._print(f"UPDATE {self._get_table_name(node.table)} SET ")
|
|
279
335
|
self._join(node.set)
|
|
@@ -286,13 +342,16 @@ class Printer(BasePrinter):
|
|
|
286
342
|
self._indent_print(indent, "SELECT ")
|
|
287
343
|
if node.distinct and node.froms:
|
|
288
344
|
self._print("DISTINCT ")
|
|
289
|
-
|
|
345
|
+
if node.vars:
|
|
346
|
+
self._join(node.vars, is_output=node.is_output or is_output)
|
|
347
|
+
else:
|
|
348
|
+
self._print("*")
|
|
290
349
|
if node.froms:
|
|
291
350
|
self._print(" FROM ")
|
|
292
351
|
if isinstance(node.froms, Select):
|
|
293
|
-
# If `froms` is a `Select`, we need to print it inline
|
|
352
|
+
# If `froms` is a `Select`, we need to print it inline, and we do not need to indent it
|
|
294
353
|
self._print("( ")
|
|
295
|
-
self.print_node(node.froms,
|
|
354
|
+
self.print_node(node.froms, 0, True)
|
|
296
355
|
self._print(" )")
|
|
297
356
|
else:
|
|
298
357
|
self._join(node.froms)
|
|
@@ -304,6 +363,9 @@ class Printer(BasePrinter):
|
|
|
304
363
|
if node.group_by:
|
|
305
364
|
self._print(" GROUP BY ")
|
|
306
365
|
self._join(node.group_by)
|
|
366
|
+
if node.order_by:
|
|
367
|
+
self._print(" ORDER BY ")
|
|
368
|
+
self._join(node.order_by)
|
|
307
369
|
if node.limit:
|
|
308
370
|
self._print(f" LIMIT {node.limit}")
|
|
309
371
|
if not inlined:
|
|
@@ -321,41 +383,48 @@ class Printer(BasePrinter):
|
|
|
321
383
|
if not inlined:
|
|
322
384
|
self._print(";")
|
|
323
385
|
elif isinstance(node, VarRef):
|
|
386
|
+
# --- Handle column or name ---
|
|
324
387
|
if node.column is None:
|
|
325
388
|
self._print(node.name)
|
|
389
|
+
elif node.column.lower() in ("any", "order"):
|
|
390
|
+
self._print(f'{node.name}."{node.column}"')
|
|
326
391
|
else:
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
392
|
+
self._print(f"{node.name}.{node.column}")
|
|
393
|
+
# --- Add CAST if needed ---
|
|
394
|
+
if node.type:
|
|
395
|
+
self._print(f"::{node.type}")
|
|
396
|
+
# --- Handle alias ---
|
|
331
397
|
if node.alias:
|
|
398
|
+
needs_quotes = any(c in node.alias for c in ['-', '?'])
|
|
332
399
|
if is_output:
|
|
333
400
|
self._print(f' as "{node.alias}"')
|
|
334
|
-
elif node.alias != node.column:
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
else:
|
|
338
|
-
self._print(f" as {node.alias}")
|
|
401
|
+
elif node.alias != node.column or node.type or needs_quotes:
|
|
402
|
+
alias_str = f'"{node.alias}"' if needs_quotes else node.alias
|
|
403
|
+
self._print(f" as {alias_str}")
|
|
339
404
|
elif isinstance(node, RowNumberVar):
|
|
405
|
+
# --- ROW_NUMBER clause ---
|
|
340
406
|
self._print("ROW_NUMBER() OVER (")
|
|
407
|
+
# --- Partition by (if present) ---
|
|
341
408
|
if node.partition_by_vars:
|
|
342
409
|
self._print(" PARTITION BY ")
|
|
343
410
|
self._join(node.partition_by_vars)
|
|
411
|
+
# --- ORDER BY ---
|
|
344
412
|
self._print(" ORDER BY ")
|
|
345
413
|
self._join(node.order_by_vars)
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
self._print(f
|
|
414
|
+
self._print(" )")
|
|
415
|
+
# --- Add CAST if needed ---
|
|
416
|
+
if node.type:
|
|
417
|
+
self._print(f"::{node.type}")
|
|
418
|
+
# --- Handle alias ---
|
|
419
|
+
alias_str = f' as "{node.alias}"' if is_output else f' as {node.alias}'
|
|
420
|
+
self._print(alias_str)
|
|
350
421
|
elif isinstance(node, OrderByVar):
|
|
351
422
|
self._print(node.var)
|
|
352
|
-
if node.is_ascending
|
|
353
|
-
|
|
354
|
-
else:
|
|
355
|
-
self._print(" DESC")
|
|
423
|
+
direction = "ASC" if node.is_ascending else "DESC"
|
|
424
|
+
self._print(f" {direction}")
|
|
356
425
|
elif isinstance(node, From):
|
|
357
426
|
self._print(self._get_table_name(node.table))
|
|
358
|
-
if node.alias
|
|
427
|
+
if node.alias:
|
|
359
428
|
self._print(f" AS {node.alias}")
|
|
360
429
|
elif isinstance(node, Join):
|
|
361
430
|
join_type = (
|
|
@@ -366,13 +435,15 @@ class Printer(BasePrinter):
|
|
|
366
435
|
self._print(f"{join_type} {self._get_table_name(node.table)}")
|
|
367
436
|
if node.alias is not None:
|
|
368
437
|
self._print(f" AS {node.alias}")
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
438
|
+
if not isinstance(node, JoinWithoutCondition):
|
|
439
|
+
self._print(" ON ")
|
|
440
|
+
if node.on is None:
|
|
441
|
+
self._print("TRUE")
|
|
442
|
+
else:
|
|
443
|
+
self.print_node(node.on, indent, True)
|
|
374
444
|
elif isinstance(node, Where):
|
|
375
|
-
|
|
445
|
+
expr = node.expression if node.expression else 'FALSE'
|
|
446
|
+
self._print(f" WHERE {expr}")
|
|
376
447
|
elif isinstance(node, RawSource):
|
|
377
448
|
self._print(node.src)
|
|
378
449
|
|
|
@@ -390,7 +461,10 @@ class Printer(BasePrinter):
|
|
|
390
461
|
self._indent_print_nl(indent + 2, "UNION ALL")
|
|
391
462
|
self.print_node(s, indent + 1, True)
|
|
392
463
|
self._nl()
|
|
393
|
-
self._print(
|
|
464
|
+
self._print(") SELECT")
|
|
465
|
+
if node.distinct:
|
|
466
|
+
self._print(" DISTINCT")
|
|
467
|
+
self._print(f" * FROM {self._get_table_name(node.name)}")
|
|
394
468
|
if not inlined:
|
|
395
469
|
self._print(";")
|
|
396
470
|
# --------------------------------------------------
|
|
@@ -3,7 +3,7 @@ from typing import Any
|
|
|
3
3
|
|
|
4
4
|
from relationalai.semantics.internal import internal as i
|
|
5
5
|
from .std import _Date, _DateTime, _Number, _String, _Integer, _make_expr
|
|
6
|
-
from . import
|
|
6
|
+
from . import datetime, math, strings, decimals, integers, floats, pragmas, constraints, re
|
|
7
7
|
|
|
8
8
|
def range(*args: _Integer) -> i.Expression:
|
|
9
9
|
# supports range(stop), range(start, stop), range(start, stop, step)
|
|
@@ -23,9 +23,12 @@ def hash(*args: Any, type=i.Hash) -> i.Expression:
|
|
|
23
23
|
raise ValueError("hash expects at least one argument")
|
|
24
24
|
return _make_expr("hash", i.TupleArg(args), type.ref("res"))
|
|
25
25
|
|
|
26
|
-
def uuid_to_string(arg:_Integer) -> i.Expression:
|
|
26
|
+
def uuid_to_string(arg: _Integer) -> i.Expression:
|
|
27
27
|
return _make_expr("uuid_to_string", arg, i.String.ref("res"))
|
|
28
28
|
|
|
29
|
+
def parse_uuid(arg: _String) -> i.Expression:
|
|
30
|
+
return _make_expr("parse_uuid", arg, i.Hash.ref("res"))
|
|
31
|
+
|
|
29
32
|
def cast(type: i.Concept, arg: _Date|_DateTime|_Number|_String) -> i.Expression:
|
|
30
33
|
return _make_expr("cast", i.TypeRef(type), arg, type.ref("res"))
|
|
31
34
|
|
|
@@ -37,13 +40,15 @@ __all__ = [
|
|
|
37
40
|
"range",
|
|
38
41
|
"hash",
|
|
39
42
|
"cast",
|
|
40
|
-
"
|
|
43
|
+
"datetime",
|
|
41
44
|
"math",
|
|
42
45
|
"strings",
|
|
46
|
+
"re",
|
|
43
47
|
"decimals",
|
|
44
48
|
"integers",
|
|
45
49
|
"floats",
|
|
46
50
|
"pragmas",
|
|
47
51
|
"constraints",
|
|
48
|
-
"uuid_to_string"
|
|
52
|
+
"uuid_to_string",
|
|
53
|
+
"parse_uuid",
|
|
49
54
|
]
|