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.
Files changed (54) hide show
  1. relationalai/clients/config.py +7 -0
  2. relationalai/clients/direct_access_client.py +113 -0
  3. relationalai/clients/snowflake.py +41 -107
  4. relationalai/clients/use_index_poller.py +349 -188
  5. relationalai/early_access/dsl/bindings/csv.py +2 -2
  6. relationalai/early_access/metamodel/rewrite/__init__.py +5 -3
  7. relationalai/early_access/rel/rewrite/__init__.py +1 -1
  8. relationalai/errors.py +24 -3
  9. relationalai/semantics/internal/annotations.py +1 -0
  10. relationalai/semantics/internal/internal.py +22 -4
  11. relationalai/semantics/lqp/builtins.py +1 -0
  12. relationalai/semantics/lqp/executor.py +61 -12
  13. relationalai/semantics/lqp/intrinsics.py +23 -0
  14. relationalai/semantics/lqp/model2lqp.py +13 -4
  15. relationalai/semantics/lqp/passes.py +4 -6
  16. relationalai/semantics/lqp/primitives.py +12 -1
  17. relationalai/semantics/{rel → lqp}/rewrite/__init__.py +6 -0
  18. relationalai/semantics/lqp/rewrite/extract_common.py +362 -0
  19. relationalai/semantics/metamodel/builtins.py +20 -2
  20. relationalai/semantics/metamodel/factory.py +3 -2
  21. relationalai/semantics/metamodel/rewrite/__init__.py +3 -9
  22. relationalai/semantics/reasoners/graph/core.py +273 -71
  23. relationalai/semantics/reasoners/optimization/solvers_dev.py +20 -1
  24. relationalai/semantics/reasoners/optimization/solvers_pb.py +24 -3
  25. relationalai/semantics/rel/builtins.py +5 -1
  26. relationalai/semantics/rel/compiler.py +7 -19
  27. relationalai/semantics/rel/executor.py +2 -2
  28. relationalai/semantics/rel/rel.py +6 -0
  29. relationalai/semantics/rel/rel_utils.py +8 -1
  30. relationalai/semantics/sql/compiler.py +122 -42
  31. relationalai/semantics/sql/executor/duck_db.py +28 -3
  32. relationalai/semantics/sql/rewrite/denormalize.py +4 -6
  33. relationalai/semantics/sql/rewrite/recursive_union.py +23 -3
  34. relationalai/semantics/sql/sql.py +27 -0
  35. relationalai/semantics/std/__init__.py +2 -1
  36. relationalai/semantics/std/datetime.py +4 -0
  37. relationalai/semantics/std/re.py +83 -0
  38. relationalai/semantics/std/strings.py +1 -1
  39. relationalai/tools/cli.py +11 -4
  40. relationalai/tools/cli_controls.py +445 -60
  41. relationalai/util/format.py +78 -1
  42. {relationalai-0.11.3.dist-info → relationalai-0.12.0.dist-info}/METADATA +7 -5
  43. {relationalai-0.11.3.dist-info → relationalai-0.12.0.dist-info}/RECORD +51 -50
  44. relationalai/semantics/metamodel/rewrite/gc_nodes.py +0 -58
  45. relationalai/semantics/metamodel/rewrite/list_types.py +0 -109
  46. relationalai/semantics/rel/rewrite/extract_common.py +0 -451
  47. /relationalai/semantics/{rel → lqp}/rewrite/cdc.py +0 -0
  48. /relationalai/semantics/{metamodel → lqp}/rewrite/extract_keys.py +0 -0
  49. /relationalai/semantics/{metamodel → lqp}/rewrite/fd_constraints.py +0 -0
  50. /relationalai/semantics/{rel → lqp}/rewrite/quantify_vars.py +0 -0
  51. /relationalai/semantics/{metamodel → lqp}/rewrite/splinter.py +0 -0
  52. {relationalai-0.11.3.dist-info → relationalai-0.12.0.dist-info}/WHEEL +0 -0
  53. {relationalai-0.11.3.dist-info → relationalai-0.12.0.dist-info}/entry_points.txt +0 -0
  54. {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 group in recursive_groups.values():
53
- if group:
54
- new_body.append(f.union(list(group)))
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.Int128.ref("res"))
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 relationalai.clients import azure
20
- from typing import Sequence, cast, Any, List
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]: