relationalai 0.11.3__py3-none-any.whl → 0.12.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- relationalai/clients/config.py +7 -0
- relationalai/clients/direct_access_client.py +113 -0
- relationalai/clients/snowflake.py +41 -107
- relationalai/clients/use_index_poller.py +349 -188
- relationalai/early_access/dsl/bindings/csv.py +2 -2
- relationalai/early_access/metamodel/rewrite/__init__.py +5 -3
- relationalai/early_access/rel/rewrite/__init__.py +1 -1
- relationalai/errors.py +24 -3
- relationalai/semantics/internal/annotations.py +1 -0
- relationalai/semantics/internal/internal.py +22 -4
- relationalai/semantics/lqp/builtins.py +1 -0
- relationalai/semantics/lqp/executor.py +61 -12
- relationalai/semantics/lqp/intrinsics.py +23 -0
- relationalai/semantics/lqp/model2lqp.py +13 -4
- relationalai/semantics/lqp/passes.py +4 -6
- relationalai/semantics/lqp/primitives.py +12 -1
- relationalai/semantics/{rel → lqp}/rewrite/__init__.py +6 -0
- relationalai/semantics/lqp/rewrite/extract_common.py +362 -0
- relationalai/semantics/metamodel/builtins.py +20 -2
- relationalai/semantics/metamodel/factory.py +3 -2
- relationalai/semantics/metamodel/rewrite/__init__.py +3 -9
- relationalai/semantics/reasoners/graph/core.py +273 -71
- relationalai/semantics/reasoners/optimization/solvers_dev.py +20 -1
- relationalai/semantics/reasoners/optimization/solvers_pb.py +24 -3
- relationalai/semantics/rel/builtins.py +5 -1
- relationalai/semantics/rel/compiler.py +7 -19
- relationalai/semantics/rel/executor.py +2 -2
- relationalai/semantics/rel/rel.py +6 -0
- relationalai/semantics/rel/rel_utils.py +8 -1
- relationalai/semantics/sql/compiler.py +122 -42
- relationalai/semantics/sql/executor/duck_db.py +28 -3
- relationalai/semantics/sql/rewrite/denormalize.py +4 -6
- relationalai/semantics/sql/rewrite/recursive_union.py +23 -3
- relationalai/semantics/sql/sql.py +27 -0
- relationalai/semantics/std/__init__.py +2 -1
- relationalai/semantics/std/datetime.py +4 -0
- relationalai/semantics/std/re.py +83 -0
- relationalai/semantics/std/strings.py +1 -1
- relationalai/tools/cli.py +11 -4
- relationalai/tools/cli_controls.py +445 -60
- relationalai/util/format.py +78 -1
- {relationalai-0.11.3.dist-info → relationalai-0.12.0.dist-info}/METADATA +7 -5
- {relationalai-0.11.3.dist-info → relationalai-0.12.0.dist-info}/RECORD +51 -50
- relationalai/semantics/metamodel/rewrite/gc_nodes.py +0 -58
- relationalai/semantics/metamodel/rewrite/list_types.py +0 -109
- relationalai/semantics/rel/rewrite/extract_common.py +0 -451
- /relationalai/semantics/{rel → lqp}/rewrite/cdc.py +0 -0
- /relationalai/semantics/{metamodel → lqp}/rewrite/extract_keys.py +0 -0
- /relationalai/semantics/{metamodel → lqp}/rewrite/fd_constraints.py +0 -0
- /relationalai/semantics/{rel → lqp}/rewrite/quantify_vars.py +0 -0
- /relationalai/semantics/{metamodel → lqp}/rewrite/splinter.py +0 -0
- {relationalai-0.11.3.dist-info → relationalai-0.12.0.dist-info}/WHEEL +0 -0
- {relationalai-0.11.3.dist-info → relationalai-0.12.0.dist-info}/entry_points.txt +0 -0
- {relationalai-0.11.3.dist-info → relationalai-0.12.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
|
|
@@ -55,6 +55,17 @@ class CreateView(Node):
|
|
|
55
55
|
name: str
|
|
56
56
|
query: Union[list[Select], CTE]
|
|
57
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
|
|
68
|
+
|
|
58
69
|
@dataclass(frozen=True)
|
|
59
70
|
class Insert(Node):
|
|
60
71
|
table: str
|
|
@@ -286,6 +297,22 @@ class Printer(BasePrinter):
|
|
|
286
297
|
elif isinstance(node, CreateView):
|
|
287
298
|
self._print(f"CREATE VIEW {self._get_table_name(node.name)} AS ")
|
|
288
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("$$;")
|
|
289
316
|
elif isinstance(node, Insert):
|
|
290
317
|
self._print(f"INSERT INTO {self._get_table_name(node.table)} ")
|
|
291
318
|
if len(node.columns) > 0:
|
|
@@ -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 datetime, math, strings, decimals, integers, floats, pragmas, constraints
|
|
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)
|
|
@@ -43,6 +43,7 @@ __all__ = [
|
|
|
43
43
|
"datetime",
|
|
44
44
|
"math",
|
|
45
45
|
"strings",
|
|
46
|
+
"re",
|
|
46
47
|
"decimals",
|
|
47
48
|
"integers",
|
|
48
49
|
"floats",
|
|
@@ -153,6 +153,10 @@ class datetime:
|
|
|
153
153
|
std.cast_to_int64(day), std.cast_to_int64(hour), std.cast_to_int64(minute),
|
|
154
154
|
std.cast_to_int64(second), std.cast_to_int64(millisecond), tz, b.DateTime.ref("res"))
|
|
155
155
|
|
|
156
|
+
@classmethod
|
|
157
|
+
def now(cls) -> b.Expression:
|
|
158
|
+
return _make_expr("datetime_now", b.DateTime.ref("res"))
|
|
159
|
+
|
|
156
160
|
@classmethod
|
|
157
161
|
def year(cls, datetime: _DateTime, tz: dt.tzinfo|_String|None = None) -> b.Expression:
|
|
158
162
|
tz = _extract_tz(datetime, tz)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from relationalai.semantics.internal import internal as i
|
|
4
|
+
from relationalai.semantics.metamodel.util import OrderedSet
|
|
5
|
+
from .std import _Integer, _String, _make_expr
|
|
6
|
+
from typing import Literal, Any
|
|
7
|
+
from .. import std
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def escape(regex: _String) -> i.Expression:
|
|
11
|
+
return _make_expr("escape_regex_metachars", regex, i.String.ref())
|
|
12
|
+
|
|
13
|
+
class Match(i.Producer):
|
|
14
|
+
|
|
15
|
+
def __init__(self, regex: _String, string: _String, pos: _Integer = 0, _type: Literal["search", "fullmatch", "match"] = "match"):
|
|
16
|
+
super().__init__(i.find_model([regex, string, pos]))
|
|
17
|
+
self.regex = regex
|
|
18
|
+
self.string = string
|
|
19
|
+
self.pos = pos
|
|
20
|
+
|
|
21
|
+
if _type == "match":
|
|
22
|
+
self._expr = _regex_match_all(self.regex, self.string, std.cast_to_int64(self.pos + 1))
|
|
23
|
+
self._offset, self._full_match = self._expr._arg_ref(2), self._expr._arg_ref(3)
|
|
24
|
+
elif _type == "search":
|
|
25
|
+
raise NotImplementedError("`search` is not implemented")
|
|
26
|
+
elif _type == "fullmatch":
|
|
27
|
+
_exp = _regex_match_all(self.regex, self.string, std.cast_to_int64(self.pos + 1))
|
|
28
|
+
self._offset, self._full_match = _exp._arg_ref(2), _exp._arg_ref(3)
|
|
29
|
+
self._expr = self._full_match == std.strings.substring(self.string, std.cast_to_int64(self.pos), std.strings.len(self.string))
|
|
30
|
+
|
|
31
|
+
def group(self, index: _Integer = 0) -> i.Producer:
|
|
32
|
+
if index == 0:
|
|
33
|
+
return self._full_match
|
|
34
|
+
else:
|
|
35
|
+
return _make_expr("capture_group_by_index", self.regex, self.string, std.cast_to_int64(self.pos + 1), std.cast_to_int64(index), i.String.ref("res"))
|
|
36
|
+
|
|
37
|
+
def group_by_name(self, name: _String) -> i.Producer:
|
|
38
|
+
return _make_expr("capture_group_by_name", self.regex, self.string, std.cast_to_int64(self.pos + 1), name, i.String.ref("res"))
|
|
39
|
+
|
|
40
|
+
def start(self) -> i.Expression:
|
|
41
|
+
return self._offset - 1
|
|
42
|
+
|
|
43
|
+
def end(self) -> i.Expression:
|
|
44
|
+
return std.strings.len(self.group(0)) + self.start() - 1
|
|
45
|
+
|
|
46
|
+
def span(self) -> tuple[i.Producer, i.Producer]:
|
|
47
|
+
return self.start(), self.end()
|
|
48
|
+
|
|
49
|
+
def _to_keys(self) -> OrderedSet[Any]:
|
|
50
|
+
return i.find_keys(self._expr)
|
|
51
|
+
|
|
52
|
+
def _compile_lookup(self, compiler:i.Compiler, ctx:i.CompilerContext):
|
|
53
|
+
compiler.lookup(self.regex, ctx)
|
|
54
|
+
compiler.lookup(self.string, ctx)
|
|
55
|
+
compiler.lookup(self.pos, ctx)
|
|
56
|
+
return compiler.lookup(self._expr, ctx)
|
|
57
|
+
|
|
58
|
+
def __getattr__(self, name: str) -> Any:
|
|
59
|
+
return object.__getattribute__(self, name)
|
|
60
|
+
|
|
61
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
62
|
+
object.__setattr__(self, name, value)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def match(regex: _String, string: _String) -> Match:
|
|
66
|
+
return Match(regex, string)
|
|
67
|
+
|
|
68
|
+
def search(regex: _String, string: _String, pos: _Integer = 0) -> Match:
|
|
69
|
+
return Match(regex, string, pos, _type="search")
|
|
70
|
+
|
|
71
|
+
def fullmatch(regex: _String, string: _String, pos: _Integer = 0) -> Match:
|
|
72
|
+
return Match(regex, string, pos, _type="fullmatch")
|
|
73
|
+
|
|
74
|
+
def findall(regex: _String, string: _String) -> tuple[i.Producer, i.Producer]:
|
|
75
|
+
exp = _regex_match_all(regex, string)
|
|
76
|
+
ix, match = exp._arg_ref(2), exp._arg_ref(3)
|
|
77
|
+
rank = i.rank(i.asc(ix, match))
|
|
78
|
+
return rank, match
|
|
79
|
+
|
|
80
|
+
def _regex_match_all(regex: _String, string: _String, pos: _Integer|None = None) -> i.Expression:
|
|
81
|
+
if pos is None:
|
|
82
|
+
pos = i.Int64.ref()
|
|
83
|
+
return _make_expr("regex_match_all", regex, string, pos, i.String.ref())
|
|
@@ -15,7 +15,7 @@ def concat(s0: _String, s1: _String, *args: _String) -> b.Expression:
|
|
|
15
15
|
return res
|
|
16
16
|
|
|
17
17
|
def len(s: _String) -> b.Expression:
|
|
18
|
-
return _make_expr("num_chars", s, b.
|
|
18
|
+
return _make_expr("num_chars", s, b.Int64.ref("res"))
|
|
19
19
|
|
|
20
20
|
def startswith(s0: _String, s1: _String) -> b.Expression:
|
|
21
21
|
return _make_expr("starts_with", s0, s1)
|
relationalai/tools/cli.py
CHANGED
|
@@ -16,8 +16,10 @@ from InquirerPy.base.control import Choice
|
|
|
16
16
|
from relationalai.clients.util import IdentityParser
|
|
17
17
|
from .cli_controls import divider, Spinner
|
|
18
18
|
from . import cli_controls as controls
|
|
19
|
-
from
|
|
20
|
-
|
|
19
|
+
from typing import Sequence, cast, Any, List, TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from relationalai.clients import azure
|
|
21
23
|
from relationalai.errors import RAIException
|
|
22
24
|
from relationalai.loaders.types import LoadType, UnsupportedTypeError
|
|
23
25
|
from relationalai.loaders.csv import CSVLoader
|
|
@@ -1150,9 +1152,14 @@ def import_source_flow(provider: ResourcesBase) -> Sequence[ImportSource]:
|
|
|
1150
1152
|
|
|
1151
1153
|
if isinstance(provider, snowflake.Resources):
|
|
1152
1154
|
return snowflake_import_source_flow(provider)
|
|
1153
|
-
elif isinstance(provider, azure.Resources):
|
|
1154
|
-
return azure_import_source_flow(provider)
|
|
1155
1155
|
else:
|
|
1156
|
+
# Lazy import for azure to avoid optional dependency issues
|
|
1157
|
+
try:
|
|
1158
|
+
from relationalai.clients import azure
|
|
1159
|
+
if isinstance(provider, azure.Resources):
|
|
1160
|
+
return azure_import_source_flow(provider)
|
|
1161
|
+
except ImportError:
|
|
1162
|
+
pass
|
|
1156
1163
|
raise Exception(f"No import source flow available for {provider_type.__module__}.{provider_type.__name__}")
|
|
1157
1164
|
|
|
1158
1165
|
def snowflake_import_source_flow(provider: snowflake.Resources) -> Sequence[ImportSource]:
|