relationalai 1.0.0a2__py3-none-any.whl → 1.0.0a4__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 (57) hide show
  1. relationalai/config/shims.py +1 -0
  2. relationalai/semantics/__init__.py +7 -1
  3. relationalai/semantics/frontend/base.py +19 -13
  4. relationalai/semantics/frontend/core.py +30 -2
  5. relationalai/semantics/frontend/front_compiler.py +38 -11
  6. relationalai/semantics/frontend/pprint.py +1 -1
  7. relationalai/semantics/metamodel/rewriter.py +6 -2
  8. relationalai/semantics/metamodel/typer.py +70 -26
  9. relationalai/semantics/reasoners/__init__.py +11 -0
  10. relationalai/semantics/reasoners/graph/__init__.py +38 -0
  11. relationalai/semantics/reasoners/graph/core.py +9015 -0
  12. relationalai/shims/executor.py +4 -1
  13. relationalai/shims/hoister.py +9 -0
  14. relationalai/shims/mm2v0.py +47 -34
  15. relationalai/tools/cli/cli.py +138 -0
  16. relationalai/tools/cli/docs.py +394 -0
  17. {relationalai-1.0.0a2.dist-info → relationalai-1.0.0a4.dist-info}/METADATA +5 -3
  18. {relationalai-1.0.0a2.dist-info → relationalai-1.0.0a4.dist-info}/RECORD +57 -43
  19. v0/relationalai/__init__.py +69 -22
  20. v0/relationalai/clients/__init__.py +15 -2
  21. v0/relationalai/clients/client.py +4 -4
  22. v0/relationalai/clients/exec_txn_poller.py +91 -0
  23. v0/relationalai/clients/local.py +5 -5
  24. v0/relationalai/clients/resources/__init__.py +8 -0
  25. v0/relationalai/clients/{azure.py → resources/azure/azure.py} +12 -12
  26. v0/relationalai/clients/resources/snowflake/__init__.py +20 -0
  27. v0/relationalai/clients/resources/snowflake/cli_resources.py +87 -0
  28. v0/relationalai/clients/resources/snowflake/direct_access_resources.py +717 -0
  29. v0/relationalai/clients/resources/snowflake/engine_state_handlers.py +309 -0
  30. v0/relationalai/clients/resources/snowflake/error_handlers.py +199 -0
  31. v0/relationalai/clients/resources/snowflake/resources_factory.py +99 -0
  32. v0/relationalai/clients/{snowflake.py → resources/snowflake/snowflake.py} +642 -1399
  33. v0/relationalai/clients/{use_index_poller.py → resources/snowflake/use_index_poller.py} +51 -12
  34. v0/relationalai/clients/resources/snowflake/use_index_resources.py +188 -0
  35. v0/relationalai/clients/resources/snowflake/util.py +387 -0
  36. v0/relationalai/early_access/dsl/ir/executor.py +4 -4
  37. v0/relationalai/early_access/dsl/snow/api.py +2 -1
  38. v0/relationalai/errors.py +18 -0
  39. v0/relationalai/experimental/solvers.py +7 -7
  40. v0/relationalai/semantics/devtools/benchmark_lqp.py +4 -5
  41. v0/relationalai/semantics/devtools/extract_lqp.py +1 -1
  42. v0/relationalai/semantics/internal/snowflake.py +1 -1
  43. v0/relationalai/semantics/lqp/executor.py +7 -12
  44. v0/relationalai/semantics/lqp/rewrite/extract_keys.py +25 -3
  45. v0/relationalai/semantics/metamodel/util.py +6 -5
  46. v0/relationalai/semantics/reasoners/optimization/solvers_pb.py +335 -84
  47. v0/relationalai/semantics/rel/executor.py +14 -11
  48. v0/relationalai/semantics/sql/executor/snowflake.py +9 -5
  49. v0/relationalai/semantics/tests/test_snapshot_abstract.py +1 -1
  50. v0/relationalai/tools/cli.py +26 -30
  51. v0/relationalai/tools/cli_helpers.py +10 -2
  52. v0/relationalai/util/otel_configuration.py +2 -1
  53. v0/relationalai/util/otel_handler.py +1 -1
  54. {relationalai-1.0.0a2.dist-info → relationalai-1.0.0a4.dist-info}/WHEEL +0 -0
  55. {relationalai-1.0.0a2.dist-info → relationalai-1.0.0a4.dist-info}/entry_points.txt +0 -0
  56. {relationalai-1.0.0a2.dist-info → relationalai-1.0.0a4.dist-info}/top_level.txt +0 -0
  57. /v0/relationalai/clients/{cache_store.py → resources/snowflake/cache_store.py} +0 -0
@@ -10,7 +10,10 @@ from v0.relationalai.semantics.rel.executor import RelExecutor
10
10
  from v0.relationalai.semantics.metamodel import ir as v0, factory as v0_factory
11
11
  from v0.relationalai.semantics.metamodel.visitor import collect_by_type
12
12
  from v0.relationalai.semantics.snowflake import Table as v0Table
13
- from v0.relationalai.clients.snowflake import Provider as v0Provider
13
+ try:
14
+ from v0.relationalai.clients.snowflake import Provider as v0Provider #type: ignore
15
+ except ImportError:
16
+ from v0.relationalai.clients.resources.snowflake import Provider as v0Provider
14
17
  from v0.relationalai.clients.config import Config
15
18
 
16
19
  # from ..config import Config
@@ -130,6 +130,15 @@ class Hoister(ContainerWalker):
130
130
  self.hoists[child] = parent_hoists
131
131
  self.allowed_vars[child] = allowed
132
132
  self.allowed_vars[container] = allowed
133
+ # this might be a match/union that just acts as a filter for some sibling logical,
134
+ # so we need to create an artificial demand for the allowed vars in the parent container
135
+ # to hoist them up if there's no other output providing them.
136
+ # ex: define(foo(Person)).where(union(Person.age > 10, Person.age < 5))
137
+ # parent = self.scope.parent.get(container, None)
138
+ # if parent:
139
+ # for allowed_var in allowed:
140
+ # self._register_use(allowed_var, input=True, container=parent)
141
+ # self.scope.var_refs[allowed_var]
133
142
 
134
143
  if container.scope:
135
144
  # when leaving the scope, compute hoisted vars
@@ -217,7 +217,7 @@ class Translator():
217
217
  "like": "like_match",
218
218
  "regex_escape": "escape_regex_metachars",
219
219
  }
220
- for lib in ["core", "aggregates", "common", "datetime", "floats", "math", "numbers", "strings", "re", "lqp"]:
220
+ for lib in ["core", "aggregates", "constraints", "common", "datetime", "floats", "math", "numbers", "strings", "re", "lqp"]:
221
221
  for x in b._libraries[lib].data.values():
222
222
  v0_name = RENAMES.get(x.name, x.name)
223
223
  if v0_name in v0_builtins.builtin_relations_by_name:
@@ -320,7 +320,13 @@ class Translator():
320
320
 
321
321
  def translate_annotation(self, a: mm.Annotation, parent, ctx) -> v0.Annotation:
322
322
  if a.relation.name in v0_builtins.builtin_annotations_by_name:
323
- return getattr(v0_builtins, a.relation.name + "_annotation") # type: ignore
323
+ if not a.args:
324
+ return getattr(v0_builtins, a.relation.name + "_annotation") # type: ignore
325
+ else:
326
+ return v0.Annotation(
327
+ relation=getattr(v0_builtins, a.relation.name), # type: ignore
328
+ args=tuple(self.translate_value(arg, a, ctx) for arg in a.args)
329
+ )
324
330
 
325
331
  return v0.Annotation(
326
332
  relation=self.translate_node(a.relation, a, Context.MODEL), # type: ignore
@@ -335,7 +341,7 @@ class Translator():
335
341
  # Abstract types (should be removed once our typer is done because they should not
336
342
  # show up in the typed metamodel.)
337
343
  b.core.Any: v0_types.Any,
338
- # b.core.AnyEntity: v0_types.AnyEntity,
344
+ b.core.AnyEntity: v0_types.AnyEntity,
339
345
  b.core.Number: v0_types.Number, # v0 typer can figure the rest out
340
346
  # Concrete types
341
347
  b.core.Boolean: v0_types.Bool,
@@ -362,6 +368,7 @@ class Translator():
362
368
  annotations.append(v0_builtins.external_annotation)
363
369
  name = self.translate_table_name(t)
364
370
  fields.insert(0, v0.Field(name="symbol", type=v0_types.Symbol, input=False)) # type: ignore
371
+ annotations.extend(self.translate_seq(t.annotations, t, ctx)) # type: ignore
365
372
 
366
373
  type_relation = v0.Relation(
367
374
  name=name,
@@ -372,9 +379,8 @@ class Translator():
372
379
  )
373
380
  for super_type in t.super_types:
374
381
  super_rel = self.translate_node(super_type, t, Context.TASK)
375
- if not super_rel:
382
+ if not isinstance(super_rel, v0.Relation):
376
383
  continue
377
- assert isinstance(super_rel, v0.Relation)
378
384
  v = v0.Var(type=actual_type, name=type_relation.name.lower())
379
385
  self.maintenance_rules.append(v0.Logical(
380
386
  engine=None,
@@ -456,6 +462,17 @@ class Translator():
456
462
  def translate_table(self, t: mm.Table, parent, ctx):
457
463
  return self.translate_scalartype(t, parent, ctx)
458
464
 
465
+ def rewrite_cdc_args(self, l: mm.Lookup, args, parent, ctx):
466
+ # If this is a lookup for an external table column,
467
+ # we have to prepend the column symbol to the args
468
+ root_type = l.relation.fields[0].type
469
+ if isinstance(root_type, mm.Table):
470
+ self.used_tables.add(root_type)
471
+ is_table = l.relation == root_type
472
+ if is_table or l.relation in root_type.columns:
473
+ sym = "METADATA$KEY" if is_table else l.relation.name
474
+ args = (v0.Literal(type=v0_types.Symbol, value=sym), *args)
475
+ return args
459
476
 
460
477
  # -----------------------------------------------------------------------------
461
478
  # Values
@@ -734,7 +751,6 @@ class Translator():
734
751
  assert isinstance(r.domain, mm.Logical)
735
752
  assert isinstance(r.check, mm.Logical)
736
753
  if not r.domain.body and all(isinstance(c, mm.Lookup) and c.relation is b.constraints.unique_fields for c in r.check.body):
737
- return
738
754
  v0_reqs = []
739
755
  # v0 expects a check with both the domain and the relation in it followed by the unique
740
756
  # constraint, so we construct a logical with all of that in it
@@ -743,7 +759,7 @@ class Translator():
743
759
  fields = c.args[0] # tuple of fields
744
760
  assert isinstance(fields, tuple) and all(isinstance(f, mm.Field) for f in fields)
745
761
  first_field = fields[0]
746
- if isinstance(first_field.type, mm.Table):
762
+ if isinstance(first_field.type, mm.Table): #type: ignore
747
763
  # skip union types for now
748
764
  continue
749
765
  assert isinstance(first_field, mm.Field)
@@ -768,17 +784,17 @@ class Translator():
768
784
  ))
769
785
  return v0_reqs # type: ignore
770
786
 
771
- # check = v0.Check(
772
- # check=self.translate_node(r.check, r, ctx), # type: ignore
773
- # error=self.translate_node(r.error, r, ctx) if r.error else None, # type: ignore
774
- # annotations=self.translate_frozen(r.annotations, r, ctx) # type: ignore
775
- # )
776
- # return v0.Require(
777
- # engine=self.translate_reasoner(r.reasoner, r, Context.MODEL),
778
- # domain=self.translate_node(r.domain, r, ctx), # type: ignore
779
- # checks=(check,), # type: ignore
780
- # annotations=self.translate_frozen(r.annotations, r, ctx) # type: ignore
781
- # )
787
+ check = v0.Check(
788
+ check=self.translate_node(r.check, r, ctx), # type: ignore
789
+ error=self.translate_node(r.error, r, ctx) if r.error else None, # type: ignore
790
+ annotations=self.translate_frozen(r.annotations, r, ctx) # type: ignore
791
+ )
792
+ return v0.Require(
793
+ engine=self.translate_reasoner(r.reasoner, r, Context.MODEL),
794
+ domain=self.translate_node(r.domain, r, ctx), # type: ignore
795
+ checks=(check,), # type: ignore
796
+ annotations=self.translate_frozen(r.annotations, r, ctx) # type: ignore
797
+ )
782
798
  return None
783
799
 
784
800
  # -----------------------------
@@ -851,19 +867,11 @@ class Translator():
851
867
  if relation is None:
852
868
  return None
853
869
 
854
- # External Table Column lookups
855
- # we have to prepend the column symbol to the args
856
- root_type = l.relation.fields[0].type
857
- if isinstance(root_type, mm.Table):
858
- self.used_tables.add(root_type)
859
- is_table = l.relation == root_type
860
- if is_table or l.relation in root_type.columns:
861
- sym = "METADATA$KEY" if is_table else l.relation.name
862
- args = (v0.Literal(type=v0_types.Symbol, value=sym), *args)
863
870
 
864
871
  # Specific rewrites
865
872
  rewrite = self.rewrite_lookup(l, parent, ctx)
866
873
  if rewrite is None:
874
+ args = self.rewrite_cdc_args(l, args, parent, ctx)
867
875
  # General translation
868
876
  rewrite = v0.Lookup(
869
877
  engine=self.translate_reasoner(l.reasoner, l, Context.MODEL),
@@ -1100,16 +1108,18 @@ class Translator():
1100
1108
  else:
1101
1109
  replaced_args.append(arg)
1102
1110
  # translate the lookup
1111
+ args = tuple(self.translate_value(arg, l, ctx) for arg in replaced_args)
1112
+ args = self.rewrite_cdc_args(l, args, l, ctx)
1103
1113
  lookup = v0.Lookup(
1104
1114
  engine=self.translate_reasoner(l.reasoner, l, Context.MODEL),
1105
1115
  relation=self.translate_node(l.relation), # type: ignore
1106
- args=tuple(self.translate_value(arg, l, ctx) for arg in replaced_args),
1116
+ args=args,
1107
1117
  annotations=self.translate_frozen(l.annotations, l, ctx) # type: ignore
1108
1118
  )
1109
1119
  # subtract 1 from the index to convert from 1-based to 0-based
1110
1120
  tasks = []
1111
1121
  for index in indices:
1112
- arg = args[index]
1122
+ arg = l.args[index]
1113
1123
  if isinstance(arg, mm.Literal):
1114
1124
  if l.relation.fields[index].input:
1115
1125
  new = mm.Literal(arg.type, arg.value + 1) # type: ignore
@@ -1117,7 +1127,7 @@ class Translator():
1117
1127
  new = mm.Literal(arg.type, arg.value + 1) # type: ignore
1118
1128
  tasks.append(v0.Lookup(
1119
1129
  engine=None,
1120
- relation=v0_builtins.eq, # type: ignore
1130
+ relation=v0_builtins.eq,
1121
1131
  args=(
1122
1132
  self.translate_value(tmps[index], lookup, ctx),
1123
1133
  self.translate_value(new, lookup, ctx),
@@ -1126,9 +1136,9 @@ class Translator():
1126
1136
  elif l.relation.fields[index].input:
1127
1137
  tasks.append(v0.Lookup(
1128
1138
  engine=None,
1129
- relation=v0_builtins.plus, # type: ignore
1139
+ relation=v0_builtins.plus,
1130
1140
  args=(
1131
- self.translate_value(args[index], lookup, ctx),
1141
+ self.translate_value(arg, lookup, ctx),
1132
1142
  v0.Literal(v0_types.Int128, 1),
1133
1143
  self.translate_value(tmps[index], lookup, ctx),
1134
1144
  ),
@@ -1141,7 +1151,7 @@ class Translator():
1141
1151
  args=(
1142
1152
  self.translate_value(tmps[index], lookup, ctx),
1143
1153
  v0.Literal(v0_types.Int128, 1),
1144
- self.translate_value(args[index], lookup, ctx),
1154
+ self.translate_value(arg, lookup, ctx),
1145
1155
  ),
1146
1156
  )
1147
1157
  )
@@ -1212,7 +1222,10 @@ class Translator():
1212
1222
  if target_type is None or not isinstance(arg, (v0.Var, v0.Literal)) or arg.type == target_type:
1213
1223
  args.append(arg)
1214
1224
  continue
1215
- if field.input:
1225
+ if isinstance(arg, v0.Literal):
1226
+ # for literals, we can just create a new literal with the target type
1227
+ args.append(v0.Literal(type=target_type, value=arg.value))
1228
+ elif field.input:
1216
1229
  cast_var = v0.Var(type=target_type, name="cast_var")
1217
1230
  args.append(cast_var)
1218
1231
  inputs.append(v0.Lookup(
@@ -5,6 +5,7 @@ For more documentation on these commands, visit: https://docs.relationalai.com
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
+ import sys
8
9
  from pathlib import Path
9
10
 
10
11
  import click
@@ -86,5 +87,142 @@ def test():
86
87
  console.print("test")
87
88
 
88
89
 
90
+ @cli.group()
91
+ def docs():
92
+ """Generate and serve documentation site."""
93
+ pass
94
+
95
+
96
+ @docs.command()
97
+ @click.option(
98
+ "--output",
99
+ "-o",
100
+ default=None,
101
+ help="Output directory for generated documentation (defaults to docs/dist)",
102
+ )
103
+ def generate(output: str | None):
104
+ """Generate static documentation site from models."""
105
+ from .docs import generate_docs
106
+
107
+ try:
108
+ generate_docs(output_dir=output)
109
+ except click.ClickException as e:
110
+ # Print formatted error message and exit
111
+ console.print(f"[red]✗ Failed to generate documentation: {e.message}[/red]")
112
+ sys.exit(1)
113
+ except Exception as e:
114
+ console.print(f"[red]✗ Failed to generate documentation: {e}[/red]")
115
+ sys.exit(1)
116
+
117
+
118
+ @docs.command()
119
+ @click.option(
120
+ "--port",
121
+ "-p",
122
+ default=8000,
123
+ type=int,
124
+ help="Port to serve documentation on (default: 8000)",
125
+ )
126
+ @click.option(
127
+ "--host",
128
+ default="127.0.0.1",
129
+ help="Host to bind to (default: 127.0.0.1)",
130
+ )
131
+ @click.option(
132
+ "--build-dir",
133
+ default=None,
134
+ help="Directory containing built documentation (defaults to docs/dist)",
135
+ )
136
+ @click.option(
137
+ "--no-open",
138
+ is_flag=True,
139
+ help="Don't open browser automatically",
140
+ )
141
+ def serve(port: int, host: str, build_dir: str | None, no_open: bool):
142
+ """Serve generated documentation site."""
143
+ from .docs import find_docs_dir, serve_docs
144
+
145
+ # Determine build directory
146
+ if build_dir is None:
147
+ docs_dir = find_docs_dir()
148
+ build_dir = str(docs_dir / "dist")
149
+
150
+ try:
151
+ serve_docs(
152
+ build_dir=build_dir,
153
+ host=host,
154
+ port=port,
155
+ open_browser=not no_open,
156
+ )
157
+ except click.ClickException as e:
158
+ # Print formatted error message and exit
159
+ console.print(f"[red]✗ Failed to start server: {e.message}[/red]")
160
+ sys.exit(1)
161
+ except Exception as e:
162
+ console.print(f"[red]✗ Failed to start server: {e}[/red]")
163
+ sys.exit(1)
164
+
165
+
166
+ @docs.command()
167
+ @click.option(
168
+ "--port",
169
+ "-p",
170
+ default=5173,
171
+ type=int,
172
+ help="Port to serve development server on (default: 5173)",
173
+ )
174
+ @click.option(
175
+ "--host",
176
+ default="127.0.0.1",
177
+ help="Host to bind to (default: 127.0.0.1)",
178
+ )
179
+ @click.option(
180
+ "--no-open",
181
+ is_flag=True,
182
+ help="Don't open browser automatically",
183
+ )
184
+ def dev(port: int, host: str, no_open: bool):
185
+ """Start development server with hot module replacement (HMR).
186
+
187
+ This command runs Vite's dev server which provides:
188
+ - Hot module replacement - changes reflect instantly
189
+ - Fast refresh - no need to rebuild on every change
190
+ - Source maps for debugging
191
+
192
+ Use this for development instead of 'rai docs generate' + 'rai docs serve'.
193
+ """
194
+ from .docs import dev_docs
195
+
196
+ try:
197
+ dev_docs(
198
+ host=host,
199
+ port=port,
200
+ open_browser=not no_open,
201
+ )
202
+ except click.ClickException as e:
203
+ # Print formatted error message and exit
204
+ console.print(f"[red]✗ Failed to start dev server: {e.message}[/red]")
205
+ sys.exit(1)
206
+ except Exception as e:
207
+ console.print(f"[red]✗ Failed to start dev server: {e}[/red]")
208
+ sys.exit(1)
209
+
210
+
211
+ @docs.command(name="clean")
212
+ def clean_docs():
213
+ """Remove auto generated files and directories."""
214
+ from .docs import cleanup_docs
215
+
216
+ try:
217
+ cleanup_docs()
218
+ except click.ClickException as e:
219
+ # Print formatted error message and exit
220
+ console.print(f"[red]✗ Failed to cleanup: {e.message}[/red]")
221
+ sys.exit(1)
222
+ except Exception as e:
223
+ console.print(f"[red]✗ Failed to cleanup: {e}[/red]")
224
+ sys.exit(1)
225
+
226
+
89
227
  if __name__ == "__main__":
90
228
  cli()