relationalai 1.0.0a3__py3-none-any.whl → 1.0.0a5__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 (118) hide show
  1. relationalai/config/config.py +47 -21
  2. relationalai/config/connections/__init__.py +5 -2
  3. relationalai/config/connections/duckdb.py +2 -2
  4. relationalai/config/connections/local.py +31 -0
  5. relationalai/config/connections/snowflake.py +0 -1
  6. relationalai/config/external/raiconfig_converter.py +235 -0
  7. relationalai/config/external/raiconfig_models.py +202 -0
  8. relationalai/config/external/utils.py +31 -0
  9. relationalai/config/shims.py +1 -0
  10. relationalai/semantics/__init__.py +10 -8
  11. relationalai/semantics/backends/sql/sql_compiler.py +1 -4
  12. relationalai/semantics/experimental/__init__.py +0 -0
  13. relationalai/semantics/experimental/builder.py +295 -0
  14. relationalai/semantics/experimental/builtins.py +154 -0
  15. relationalai/semantics/frontend/base.py +67 -42
  16. relationalai/semantics/frontend/core.py +34 -6
  17. relationalai/semantics/frontend/front_compiler.py +209 -37
  18. relationalai/semantics/frontend/pprint.py +6 -2
  19. relationalai/semantics/metamodel/__init__.py +7 -0
  20. relationalai/semantics/metamodel/metamodel.py +2 -0
  21. relationalai/semantics/metamodel/metamodel_analyzer.py +58 -16
  22. relationalai/semantics/metamodel/pprint.py +6 -1
  23. relationalai/semantics/metamodel/rewriter.py +11 -7
  24. relationalai/semantics/metamodel/typer.py +116 -41
  25. relationalai/semantics/reasoners/__init__.py +11 -0
  26. relationalai/semantics/reasoners/graph/__init__.py +35 -0
  27. relationalai/semantics/reasoners/graph/core.py +9028 -0
  28. relationalai/semantics/std/__init__.py +30 -10
  29. relationalai/semantics/std/aggregates.py +641 -12
  30. relationalai/semantics/std/common.py +146 -13
  31. relationalai/semantics/std/constraints.py +71 -1
  32. relationalai/semantics/std/datetime.py +904 -21
  33. relationalai/semantics/std/decimals.py +143 -2
  34. relationalai/semantics/std/floats.py +57 -4
  35. relationalai/semantics/std/integers.py +98 -4
  36. relationalai/semantics/std/math.py +857 -35
  37. relationalai/semantics/std/numbers.py +216 -20
  38. relationalai/semantics/std/re.py +213 -5
  39. relationalai/semantics/std/strings.py +437 -44
  40. relationalai/shims/executor.py +60 -52
  41. relationalai/shims/fixtures.py +85 -0
  42. relationalai/shims/helpers.py +26 -2
  43. relationalai/shims/hoister.py +28 -9
  44. relationalai/shims/mm2v0.py +204 -173
  45. relationalai/tools/cli/cli.py +192 -10
  46. relationalai/tools/cli/components/progress_reader.py +1 -1
  47. relationalai/tools/cli/docs.py +394 -0
  48. relationalai/tools/debugger.py +11 -4
  49. relationalai/tools/qb_debugger.py +435 -0
  50. relationalai/tools/typer_debugger.py +1 -2
  51. relationalai/util/dataclasses.py +3 -5
  52. relationalai/util/docutils.py +1 -2
  53. relationalai/util/error.py +2 -5
  54. relationalai/util/python.py +23 -0
  55. relationalai/util/runtime.py +1 -2
  56. relationalai/util/schema.py +2 -4
  57. relationalai/util/structures.py +4 -2
  58. relationalai/util/tracing.py +8 -2
  59. {relationalai-1.0.0a3.dist-info → relationalai-1.0.0a5.dist-info}/METADATA +8 -5
  60. {relationalai-1.0.0a3.dist-info → relationalai-1.0.0a5.dist-info}/RECORD +118 -95
  61. {relationalai-1.0.0a3.dist-info → relationalai-1.0.0a5.dist-info}/WHEEL +1 -1
  62. v0/relationalai/__init__.py +1 -1
  63. v0/relationalai/clients/client.py +52 -18
  64. v0/relationalai/clients/exec_txn_poller.py +122 -0
  65. v0/relationalai/clients/local.py +23 -8
  66. v0/relationalai/clients/resources/azure/azure.py +36 -11
  67. v0/relationalai/clients/resources/snowflake/__init__.py +4 -4
  68. v0/relationalai/clients/resources/snowflake/cli_resources.py +12 -1
  69. v0/relationalai/clients/resources/snowflake/direct_access_resources.py +124 -100
  70. v0/relationalai/clients/resources/snowflake/engine_service.py +381 -0
  71. v0/relationalai/clients/resources/snowflake/engine_state_handlers.py +35 -29
  72. v0/relationalai/clients/resources/snowflake/error_handlers.py +43 -2
  73. v0/relationalai/clients/resources/snowflake/snowflake.py +277 -179
  74. v0/relationalai/clients/resources/snowflake/use_index_poller.py +8 -0
  75. v0/relationalai/clients/types.py +5 -0
  76. v0/relationalai/errors.py +19 -1
  77. v0/relationalai/semantics/lqp/algorithms.py +173 -0
  78. v0/relationalai/semantics/lqp/builtins.py +199 -2
  79. v0/relationalai/semantics/lqp/executor.py +68 -37
  80. v0/relationalai/semantics/lqp/ir.py +28 -2
  81. v0/relationalai/semantics/lqp/model2lqp.py +215 -45
  82. v0/relationalai/semantics/lqp/passes.py +13 -658
  83. v0/relationalai/semantics/lqp/rewrite/__init__.py +12 -0
  84. v0/relationalai/semantics/lqp/rewrite/algorithm.py +385 -0
  85. v0/relationalai/semantics/lqp/rewrite/constants_to_vars.py +70 -0
  86. v0/relationalai/semantics/lqp/rewrite/deduplicate_vars.py +104 -0
  87. v0/relationalai/semantics/lqp/rewrite/eliminate_data.py +108 -0
  88. v0/relationalai/semantics/lqp/rewrite/extract_keys.py +25 -3
  89. v0/relationalai/semantics/lqp/rewrite/period_math.py +77 -0
  90. v0/relationalai/semantics/lqp/rewrite/quantify_vars.py +65 -31
  91. v0/relationalai/semantics/lqp/rewrite/unify_definitions.py +317 -0
  92. v0/relationalai/semantics/lqp/utils.py +11 -1
  93. v0/relationalai/semantics/lqp/validators.py +14 -1
  94. v0/relationalai/semantics/metamodel/builtins.py +2 -1
  95. v0/relationalai/semantics/metamodel/compiler.py +2 -1
  96. v0/relationalai/semantics/metamodel/dependency.py +12 -3
  97. v0/relationalai/semantics/metamodel/executor.py +11 -1
  98. v0/relationalai/semantics/metamodel/factory.py +2 -2
  99. v0/relationalai/semantics/metamodel/helpers.py +7 -0
  100. v0/relationalai/semantics/metamodel/ir.py +3 -2
  101. v0/relationalai/semantics/metamodel/rewrite/dnf_union_splitter.py +30 -20
  102. v0/relationalai/semantics/metamodel/rewrite/flatten.py +50 -13
  103. v0/relationalai/semantics/metamodel/rewrite/format_outputs.py +9 -3
  104. v0/relationalai/semantics/metamodel/typer/checker.py +6 -4
  105. v0/relationalai/semantics/metamodel/typer/typer.py +4 -3
  106. v0/relationalai/semantics/metamodel/visitor.py +4 -3
  107. v0/relationalai/semantics/reasoners/optimization/solvers_dev.py +1 -1
  108. v0/relationalai/semantics/reasoners/optimization/solvers_pb.py +336 -86
  109. v0/relationalai/semantics/rel/compiler.py +2 -1
  110. v0/relationalai/semantics/rel/executor.py +3 -2
  111. v0/relationalai/semantics/tests/lqp/__init__.py +0 -0
  112. v0/relationalai/semantics/tests/lqp/algorithms.py +345 -0
  113. v0/relationalai/tools/cli.py +339 -186
  114. v0/relationalai/tools/cli_controls.py +216 -67
  115. v0/relationalai/tools/cli_helpers.py +410 -6
  116. v0/relationalai/util/format.py +5 -2
  117. {relationalai-1.0.0a3.dist-info → relationalai-1.0.0a5.dist-info}/entry_points.txt +0 -0
  118. {relationalai-1.0.0a3.dist-info → relationalai-1.0.0a5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,345 @@
1
+ """
2
+ Constructing Metamodel IR with Algorithms
3
+
4
+ We introduce a set of programmatic constructs that provide a convenient syntax for
5
+ constructing PyRel's metamodel IR representations for Loopy algorithms. Importantly, these
6
+ macros construct a new model using PyRel declarations constructed with a _base model_. The
7
+ base model needs to be also used to declare all concepts and relationships.
8
+
9
+ Below we illustrate the use of these macros by constructing a simple reachability
10
+ algorithm, whose Rel-like pseudo-code is as follows:
11
+
12
+ ```
13
+ algorithm
14
+ setup
15
+ def edge = { (1,2); (2,3); (3,4) }
16
+ def source = { 1 }
17
+ end setup
18
+ @global empty reachable = {}
19
+ loop
20
+ def frontier = source
21
+ def reachable = frontier
22
+ while (true)
23
+ def next_frontier = frontier . edge
24
+ def frontier = next_frontier
25
+ monus frontier = reachable # frontier = frontier - reachable
26
+ upsert reachable = frontier # reachable = reachable ∪ frontier
27
+ break break_reachable = empty(frontier)
28
+ end while
29
+ end loop
30
+ end algorithm
31
+ ```
32
+
33
+ The PyRel's metamodel IR for the above algorithm is constructed with the utilities as
34
+ follows.
35
+
36
+ ```
37
+ base_model = Model("algorithm_builder", dry_run=True)
38
+
39
+ # Input (context) data
40
+
41
+ edge = base_model.Relationship("Edge from {source:int} to {target:int}")
42
+ source = base_model.Relationship("Source node {node:int}")
43
+
44
+ with algorithm(base_model):
45
+ setup(
46
+ define(edge(1,2), edge(2,3), edge(3,4), edge(4,1))),
47
+ define(source(1))
48
+ )
49
+
50
+ # "local" variables and relations
51
+ n = Integer.ref()
52
+ m = Integer.ref()
53
+ reachable = base_model.Relationship("Reachable node {node:int}")
54
+ frontier = base_model.Relationship("Frontier node {node:int}")
55
+ next_frontier = base_model.Relationship("Next frontier node {node:int}")
56
+
57
+ global_(empty(define(reachable(n))))
58
+ assign(define(frontier(n)).where(source(n)))
59
+ assign(define(reachable(n)).where(frontier(n)))
60
+ with while_():
61
+ assign(define(next_frontier(m)).where(frontier(n), edge(n, m)))
62
+ assign(define(frontier(m)).where(next_frontier(m)))
63
+ monus(define(frontier(n)).where(reachable(n)))
64
+ upsert(0)(define(reachable(n)).where(frontier(n)))
65
+ break_(where(not_(frontier(n))))
66
+
67
+ # Prints the PyRel Metamodel (IR)
68
+ print(get_metamodel())
69
+
70
+ # Prints the LQP transaction
71
+ print(get_lqp_str())
72
+ ```
73
+ """
74
+ from v0.relationalai.semantics import Model
75
+ from v0.relationalai.semantics.metamodel import factory, ir, types
76
+ from v0.relationalai.semantics.internal.internal import Fragment
77
+ from v0.relationalai.semantics.lqp.algorithms import (
78
+ mk_empty, mk_assign, mk_upsert, mk_global, mk_monus
79
+ )
80
+ from v0.relationalai.semantics.lqp.constructors import mk_transaction
81
+ from v0.relationalai.semantics.lqp.compiler import Compiler
82
+ from v0.relationalai.semantics.lqp import ir as lqp, builtins
83
+ from typing import cast, TypeGuard, Optional, Sequence
84
+ from lqp import print as lqp_print
85
+ import threading
86
+ from contextlib import contextmanager
87
+
88
+
89
+ # While the constructors are very light-weight they enforce
90
+ # the following grammar for algorithms:
91
+ #
92
+ # <Algorithm> := with algorithm(base_model): <Script>
93
+ # <Script> := <Instruction>*
94
+ # <Instruction> := <BaseInstruction> | <Loop>
95
+ # <BaseInstruction> := [global_(] empty(Fragment) [)]
96
+ # | [global_(] assign(<Fragment>) [)]
97
+ # | break(<Fragment>)
98
+ # | upsert(<Int>)(<Fragment>)
99
+ # | monus(<Fragment>)
100
+ # <Loop> := with while_(): <Script>
101
+ #
102
+ # Note: global_ annotation can only be used on top-level empty and assign instructions at the
103
+ # top-level of the algorithm script.
104
+
105
+ _storage = threading.local()
106
+
107
+ def get_builder() -> 'AlgorithmBuilder':
108
+ """ Retrieves the thread-local AlgorithmBuilder instance."""
109
+ global _storage
110
+ if not(hasattr(_storage, "algorithm_builder")):
111
+ _storage.algorithm_builder = AlgorithmBuilder()
112
+ return _storage.algorithm_builder
113
+
114
+ def get_metamodel() -> ir.Model:
115
+ """ Retrieves the compiled metamodel IR for the previous algorithm. Can only be used
116
+ after an algorithm has been defined."""
117
+ return get_builder().get_metamodel()
118
+
119
+ def get_lqp_str() -> str:
120
+ """ Retrieves the LQP string representation for the previous algorithm. Can only be used
121
+ after an algorithm has been defined."""
122
+ return get_builder().get_lqp_str()
123
+
124
+ @contextmanager
125
+ def algorithm(model:Model):
126
+ """ Context manager for defining an algorithm on the given base model."""
127
+ get_builder().begin_algorithm(model)
128
+ yield
129
+ get_builder().end_algorithm()
130
+
131
+ @contextmanager
132
+ def while_():
133
+ """ Context manager for defining a while loop within an algorithm."""
134
+ get_builder().begin_while_loop()
135
+ yield
136
+ get_builder().end_while_loop()
137
+
138
+ def setup(*stmts:Fragment):
139
+ """ Defines the setup section of an algorithm: a collection of PyRel statement that
140
+ prepare input data for the algorithm."""
141
+ builder = get_builder()
142
+ assert len(builder.script_stacks) == 1, "setup can only be called at the top-level of an algorithm"
143
+ assert builder.setup_fragments is None, "setup can only be called once per algorithm"
144
+ builder.set_setup_fragments(stmts)
145
+
146
+ def global_(pos:int):
147
+ """ Marks a top-level `empty` or `assign` instruction as defining a global relation."""
148
+ assert type(pos) is int, "global_ can only be applied to empty and assign"
149
+ builder = get_builder()
150
+ assert len(builder.script_stacks) == 1, "global_ can only be applied to top-level instructions"
151
+ assert len(builder.script_stacks[0].instructions) == pos + 1
152
+ task = cast(ir.Task, mk_global(builder.script_stacks[0].instructions[pos]))
153
+ builder.script_stacks[0].instructions[pos] = task
154
+ builder.add_global_relation(task)
155
+
156
+ def empty(stmt) -> int:
157
+ """ Marks a PyRel statement as an assignment of empty relation. The statement must not
158
+ have a body (no where clause)."""
159
+ assert has_empty_body(stmt), "Empty instruction must have an empty body"
160
+ task = get_builder().compile_statement(stmt)
161
+ task = cast(ir.Task, mk_empty(task))
162
+ return get_builder().append_task(task)
163
+
164
+ def assign(stmt) -> int:
165
+ """ Marks a PyRel statement as an assignment instruction."""
166
+ task = get_builder().compile_statement(stmt)
167
+ task = cast(ir.Task, mk_assign(task))
168
+ return get_builder().append_task(task)
169
+
170
+ def upsert_with_arity(arity:int, stmt:Fragment):
171
+ task = get_builder().compile_statement(stmt)
172
+ task = cast(ir.Task, mk_upsert(task, arity))
173
+ get_builder().append_task(task)
174
+
175
+ def upsert(arity:int):
176
+ """ Marks a PyRel statement as an upsert instruction with the given arity."""
177
+ assert type(arity) is int and arity >= 0, "arity must be a non-negative integer"
178
+ return lambda stmt: upsert_with_arity(arity, stmt)
179
+
180
+ def monus(stmt: Fragment) -> int:
181
+ """ Marks a PyRel statement as a Boolean monus (set difference) instruction."""
182
+ task = get_builder().compile_statement(stmt)
183
+ task = cast(ir.Task, mk_monus(task, types.Bool, "or", 0))
184
+ return get_builder().append_task(task)
185
+
186
+ def break_(stmt):
187
+ """ Marks a PyRel statement as a break instruction. The statement must be headless (no define clause)."""
188
+ assert has_no_head(stmt), "Break instruction must have a headless fragment"
189
+ task = get_builder().compile_statement(stmt)
190
+ assert isinstance(task, ir.Logical)
191
+ break_condition = [cond for cond in task.body if not isinstance(cond, ir.Update)]
192
+ break_node = factory.break_(factory.logical(break_condition))
193
+ get_builder().append_task(break_node)
194
+
195
+ def has_empty_body(stmt) -> TypeGuard[Fragment]:
196
+ if not isinstance(stmt, Fragment):
197
+ return False
198
+ return len(stmt._where) == 0
199
+
200
+ def has_no_head(frag):
201
+ return len(frag._define) == 0
202
+
203
+
204
+ class ScriptBuilder:
205
+ """
206
+ Builder for Loopy scripts.
207
+ """
208
+ def __init__(self):
209
+ self.instructions:list[ir.Task] = []
210
+
211
+ def add_task(self, instr:ir.Task) -> int:
212
+ self.instructions.append(instr)
213
+ return len(self.instructions) - 1
214
+
215
+ def build_script(self, annos:list[ir.Annotation]) -> ir.Sequence:
216
+ return factory.sequence(
217
+ tasks=self.instructions,
218
+ annos=[builtins.script_annotation()] + annos
219
+ )
220
+
221
+
222
+ class AlgorithmBuilder:
223
+ """
224
+ Builder for Loopy algorithms.
225
+ """
226
+ def __init__(self):
227
+ self.script_stacks:list[ScriptBuilder] = []
228
+ self.compiled_model:Optional[ir.Model] = None
229
+ self.global_relations:list[str] = []
230
+ self.base_model:Optional[Model] = None
231
+ self.setup_fragments:Optional[list[Fragment]] = None
232
+
233
+ def begin_algorithm(self, base_model:Model):
234
+ self.base_model = base_model
235
+ self.script_stacks = [ScriptBuilder()]
236
+ self.compiled_model = None
237
+ self.global_relations:list[str] = []
238
+ self.setup_fragments:Optional[list[Fragment]] = None
239
+
240
+ def add_global_relation(self, task:ir.Task):
241
+ assert isinstance(task, ir.Logical)
242
+ for t in task.body:
243
+ if isinstance(t, ir.Update):
244
+ if t.relation.name not in self.global_relations:
245
+ self.global_relations.append(t.relation.name)
246
+
247
+ def set_setup_fragments(self, fragments:Sequence[Fragment]):
248
+ self.setup_fragments = list(fragments)
249
+
250
+ def compile_statement(self, stmt:Fragment) -> ir.Task:
251
+ assert self.base_model is not None
252
+ task = self.base_model._compiler.compile_task(stmt)
253
+ return task
254
+
255
+ def append_task(self, task:ir.Task) -> int:
256
+ assert len(self.script_stacks) > 0
257
+ return self.script_stacks[-1].add_task(task)
258
+
259
+ def begin_while_loop(self):
260
+ script_builder = ScriptBuilder()
261
+ self.script_stacks.append(script_builder)
262
+
263
+ def end_while_loop(self):
264
+ script_builder = self.script_stacks.pop()
265
+ while_script = script_builder.build_script([builtins.while_annotation()])
266
+ loop = factory.loop(while_script, annos=[builtins.while_annotation()])
267
+ self.append_task(loop)
268
+
269
+ def end_algorithm(self):
270
+ assert len(self.script_stacks) == 1
271
+ script_builder = self.script_stacks.pop()
272
+ algorithm_script = script_builder.build_script([builtins.algorithm_annotation()])
273
+ setup = self.compile_setup()
274
+ algorithm_logical = factory.logical(setup + [algorithm_script])
275
+ self.compiled_model = factory.compute_model(algorithm_logical)
276
+
277
+ def compile_setup(self) -> list[ir.Logical]:
278
+ if self.setup_fragments is None:
279
+ return []
280
+ assert self.setup_fragments is not None
281
+ assert self.base_model is not None
282
+ setup_tasks = []
283
+ for stmt in self.setup_fragments:
284
+ task = self.base_model._compiler.compile_task(stmt)
285
+ setup_tasks.append(task)
286
+ return setup_tasks
287
+
288
+ def get_metamodel(self) -> ir.Model:
289
+ """ Retrieves the compiled metamodel IR for the previous algorithm. """
290
+ metamodel = self.compiled_model
291
+ assert metamodel is not None, "No metamodel available. You must first define algorithm."
292
+ return metamodel
293
+
294
+ def get_lqp_str(self) -> str:
295
+ lqp = self.get_lqp()
296
+ options = lqp_print.ugly_config.copy()
297
+ options[str(lqp_print.PrettyOptions.PRINT_NAMES)] = True
298
+ options[str(lqp_print.PrettyOptions.PRINT_DEBUG)] = False
299
+ lqp_str = lqp_print.to_string(lqp, options)
300
+ return lqp_str
301
+
302
+ def get_lqp(self):
303
+ model = self.get_metamodel()
304
+
305
+ compiler = Compiler()
306
+ rewritten_model = compiler.rewrite(model)
307
+ write_epoch = compiler.do_compile(rewritten_model, {'fragment_id': b"f1"})[1]
308
+
309
+ define = cast(lqp.Define, write_epoch.writes[0].write_type)
310
+ debug_info = define.fragment.debug_info
311
+
312
+ read_epoch = self._build_read_epoch(debug_info)
313
+
314
+ transaction = mk_transaction([write_epoch, read_epoch])
315
+
316
+ return transaction
317
+
318
+ def _build_read_epoch(self, debug_info:lqp.DebugInfo) -> lqp.Epoch:
319
+ reads = []
320
+
321
+ relation_id:dict[str,lqp.RelationId] = dict()
322
+ for rel_id, rel_name in debug_info.id_to_orig_name.items():
323
+ if rel_name in self.global_relations:
324
+ relation_id[rel_name] = rel_id
325
+
326
+ global_relation_names = [rel for rel in self.global_relations if rel in relation_id]
327
+
328
+ for (i, rel_name) in enumerate(global_relation_names):
329
+ read = lqp.Read(
330
+ meta = None,
331
+ read_type = lqp.Output(
332
+ meta=None,
333
+ name=f"{rel_name}",
334
+ relation_id=relation_id[rel_name],
335
+ )
336
+ )
337
+ reads.append(read)
338
+
339
+ read_epoch = lqp.Epoch(
340
+ meta = None,
341
+ writes = [],
342
+ reads = reads,
343
+ )
344
+
345
+ return read_epoch