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
@@ -1,10 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections import defaultdict
4
- from dataclasses import dataclass, field, replace as dc_replace
5
- from typing import Dict, Sequence as seq, Iterable
4
+ from dataclasses import dataclass, field
5
+ from typing import Dict, Sequence as seq
6
6
  from enum import Enum
7
7
  from math import pi
8
+ from urllib.parse import urlparse
8
9
 
9
10
  # PyRel imports
10
11
  from ..util.naming import sanitize
@@ -12,13 +13,9 @@ from ..semantics.metamodel import metamodel as mm
12
13
  from ..semantics.metamodel.builtins import builtins as b, is_abstract
13
14
  from ..semantics.metamodel.metamodel_analyzer import Normalize
14
15
  # ensure std libs are loaded
15
- from ..semantics.metamodel.metamodel_analyzer import VarFinder
16
- from ..semantics.metamodel.rewriter import Walker
17
- from ..semantics.std import aggregates, common, math, numbers, re, strings, decimals, datetime, floats
18
- from ..semantics.backends.lqp import annotations as lqp_annotations
19
16
 
20
17
  # V0 imports
21
- from v0.relationalai.semantics.metamodel import ir as v0, builtins as v0_builtins, types as v0_types, factory as f
18
+ from v0.relationalai.semantics.metamodel import ir as v0, builtins as v0_builtins, types as v0_types
22
19
  from v0.relationalai.semantics.metamodel.util import FrozenOrderedSet, frozen, ordered_set, filter_by_type, OrderedSet
23
20
  from v0.relationalai.semantics.internal.internal import literal_value_to_type
24
21
  from v0.relationalai.semantics.metamodel.typer import typer as v0_typer
@@ -73,7 +70,10 @@ class Translator():
73
70
  maintenance_rules: list[v0.Logical] = field(default_factory=list)
74
71
 
75
72
  # outputs that are exports
76
- exports: set[v0.Output] = field(default_factory=set)
73
+ exports: dict[v0.Output, mm.Table] = field(default_factory=dict)
74
+
75
+ # output column order
76
+ output_column_order: Dict[v0.Output, int] = field(default_factory=dict)
77
77
 
78
78
  hoister = Hoister()
79
79
  normalize = Normalize()
@@ -141,7 +141,7 @@ class Translator():
141
141
  # produces. If there's a quote in the table name, then we take it verbatim, otherwise we lowercase it.
142
142
  def translate_table_name(self, table: mm.Table) -> str:
143
143
  name = IdentityParser(table.name).identifier
144
- name = name.lower() if '"' not in table.name else table.name.replace('"', '_')
144
+ name = name.lower() if '"' not in table.name else table.name.replace('"', '_').replace("-", "_")
145
145
  return sanitize(name)
146
146
 
147
147
  #-----------------------------------------------------------------------------
@@ -194,9 +194,9 @@ class Translator():
194
194
  def translate_overload(self, o: mm.Overload, parent: mm.Relation, ctx) -> v0.Relation:
195
195
  # Overloads in v0 are represented as separate Relation nodes with different types
196
196
  fields = []
197
- for field, typ in zip(parent.fields, o.types):
197
+ for fld, typ in zip(parent.fields, o.types):
198
198
  fields.append(
199
- v0.Field(name=field.name, type=self.translate_node(typ, field, ctx), input=field.input) # type: ignore
199
+ v0.Field(name=fld.name, type=self.translate_node(typ, fld, ctx), input=fld.input) # type: ignore
200
200
  )
201
201
  return v0.Relation(
202
202
  name=parent.name,
@@ -217,7 +217,9 @@ 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", "graph", "math", "numbers", "strings", "re", "lqp"]:
221
+ if lib not in b._libraries:
222
+ continue
221
223
  for x in b._libraries[lib].data.values():
222
224
  v0_name = RENAMES.get(x.name, x.name)
223
225
  if v0_name in v0_builtins.builtin_relations_by_name:
@@ -320,7 +322,13 @@ class Translator():
320
322
 
321
323
  def translate_annotation(self, a: mm.Annotation, parent, ctx) -> v0.Annotation:
322
324
  if a.relation.name in v0_builtins.builtin_annotations_by_name:
323
- return getattr(v0_builtins, a.relation.name + "_annotation") # type: ignore
325
+ if not a.args:
326
+ return getattr(v0_builtins, a.relation.name + "_annotation") # type: ignore
327
+ else:
328
+ return v0.Annotation(
329
+ relation=getattr(v0_builtins, a.relation.name), # type: ignore
330
+ args=tuple(self.translate_value(arg, a, ctx) for arg in a.args)
331
+ )
324
332
 
325
333
  return v0.Annotation(
326
334
  relation=self.translate_node(a.relation, a, Context.MODEL), # type: ignore
@@ -335,7 +343,7 @@ class Translator():
335
343
  # Abstract types (should be removed once our typer is done because they should not
336
344
  # show up in the typed metamodel.)
337
345
  b.core.Any: v0_types.Any,
338
- # b.core.AnyEntity: v0_types.AnyEntity,
346
+ b.core.AnyEntity: v0_types.AnyEntity,
339
347
  b.core.Number: v0_types.Number, # v0 typer can figure the rest out
340
348
  # Concrete types
341
349
  b.core.Boolean: v0_types.Bool,
@@ -362,6 +370,7 @@ class Translator():
362
370
  annotations.append(v0_builtins.external_annotation)
363
371
  name = self.translate_table_name(t)
364
372
  fields.insert(0, v0.Field(name="symbol", type=v0_types.Symbol, input=False)) # type: ignore
373
+ annotations.extend(self.translate_seq(t.annotations, t, ctx)) # type: ignore
365
374
 
366
375
  type_relation = v0.Relation(
367
376
  name=name,
@@ -372,9 +381,8 @@ class Translator():
372
381
  )
373
382
  for super_type in t.super_types:
374
383
  super_rel = self.translate_node(super_type, t, Context.TASK)
375
- if not super_rel:
384
+ if not isinstance(super_rel, v0.Relation):
376
385
  continue
377
- assert isinstance(super_rel, v0.Relation)
378
386
  v = v0.Var(type=actual_type, name=type_relation.name.lower())
379
387
  self.maintenance_rules.append(v0.Logical(
380
388
  engine=None,
@@ -456,15 +464,15 @@ class Translator():
456
464
  def translate_table(self, t: mm.Table, parent, ctx):
457
465
  return self.translate_scalartype(t, parent, ctx)
458
466
 
459
- def rewrite_cdc_args(self, l: mm.Lookup, args, parent, ctx):
467
+ def rewrite_cdc_args(self, lookup_node: mm.Lookup, args, parent, ctx):
460
468
  # If this is a lookup for an external table column,
461
469
  # we have to prepend the column symbol to the args
462
- root_type = l.relation.fields[0].type
470
+ root_type = lookup_node.relation.fields[0].type
463
471
  if isinstance(root_type, mm.Table):
464
472
  self.used_tables.add(root_type)
465
- is_table = l.relation == root_type
466
- if is_table or l.relation in root_type.columns:
467
- sym = "METADATA$KEY" if is_table else l.relation.name
473
+ is_table = lookup_node.relation == root_type
474
+ if is_table or lookup_node.relation in root_type.columns:
475
+ sym = "METADATA$KEY" if is_table else lookup_node.relation.name
468
476
  args = (v0.Literal(type=v0_types.Symbol, value=sym), *args)
469
477
  return args
470
478
 
@@ -488,22 +496,22 @@ class Translator():
488
496
  name=v.name
489
497
  )
490
498
 
491
- def translate_literal(self, l: mm.Literal, parent, ctx) -> v0.Literal:
499
+ def translate_literal(self, literal_node: mm.Literal, parent, ctx) -> v0.Literal:
492
500
  typ = None
493
- if l.type is None or is_abstract(l.type):
501
+ if literal_node.type is None or is_abstract(literal_node.type):
494
502
  # force int64 for literals, it can be widened later if needed
495
- if type(l.value) == int:
503
+ if isinstance(literal_node.value, int):
496
504
  typ = v0_types.Int64
497
505
  else:
498
506
  # using v0's map of literal to type
499
- typ = literal_value_to_type(l.value)
507
+ typ = literal_value_to_type(literal_node.value)
500
508
  else:
501
- typ = self.translate_node(l.type, l, Context.MODEL)
509
+ typ = self.translate_node(literal_node.type, literal_node, Context.MODEL)
502
510
  if type is None:
503
511
  pass
504
512
  return v0.Literal(
505
513
  type=typ, # type: ignore
506
- value=l.value
514
+ value=literal_node.value
507
515
  )
508
516
 
509
517
  # Value = _Union[Var, Literal, Type, Relation, seq["Value"], None]
@@ -531,7 +539,8 @@ class Translator():
531
539
  # merge all outputs, accumulating keys and aliases
532
540
  keys = ordered_set()
533
541
  aliases = ordered_set()
534
- for output in outputs:
542
+ sorted_outputs = sorted(outputs, key=lambda o: self.output_column_order.get(o, -1))
543
+ for output in sorted_outputs:
535
544
  assert isinstance(output, v0.Output)
536
545
  children.remove(output)
537
546
  if output.keys is not None:
@@ -539,9 +548,15 @@ class Translator():
539
548
  aliases.update(output.aliases)
540
549
 
541
550
  # add a single, merged output
542
- is_export = any(output in self.exports for output in outputs)
543
- annos = (v0_builtins.export_annotation,) if is_export else ()
544
- if is_export and not keys:
551
+ export_output = next((output for output in outputs if output in self.exports), None)
552
+ annos = ()
553
+ if export_output:
554
+ uri = urlparse(self.exports[export_output].uri)
555
+ if uri.scheme == "table":
556
+ fqn = uri.hostname
557
+ annos = (v0.Annotation(v0_builtins.export, (v0.Literal(type=v0_types.String, value=fqn,),)),)
558
+
559
+ if export_output and not keys:
545
560
  key_var = v0.Var(type=v0_types.Hash, name="export_key")
546
561
  vals = [alias[1] for alias in aliases]
547
562
  children.append(v0.Construct(
@@ -564,9 +579,9 @@ class Translator():
564
579
  def get_outputs(self, children: seq[v0.Node]) -> list[v0.Output]:
565
580
  return list(filter(lambda o: isinstance(o, v0.Output), children)) # type: ignore
566
581
 
567
- def translate_logical(self, l: mm.Logical, parent, ctx):
582
+ def translate_logical(self, logical_node: mm.Logical, parent, ctx):
568
583
  # first process children
569
- children = self.translate_seq(l.body, l, ctx)
584
+ children = self.translate_seq(logical_node.body, logical_node, ctx)
570
585
 
571
586
  # inline logicals if possible
572
587
  new_children = []
@@ -579,34 +594,45 @@ class Translator():
579
594
  children = tuple(new_children)
580
595
 
581
596
  # compute variables to hoist, wrapping in Default if the logical is optional
582
- if l.optional:
583
- hoisted = tuple([v0.Default(self.translate_node(v, l, ctx), None) for v in self.hoister.hoisted(l)]) # type: ignore
597
+ # NOTE: the v0 compiler stack expects the aggregate body to hoist optionally or it doesn't work
598
+ if logical_node.optional or isinstance(parent, mm.Aggregate):
599
+ hoisted = tuple([v0.Default(self.translate_node(v, logical_node, ctx), None) for v in self.hoister.hoisted(logical_node)]) # type: ignore
584
600
  else:
585
- hoisted = tuple([self.translate_node(v, l, ctx) for v in self.hoister.hoisted(l)]) # type: ignore
601
+ hoisted = tuple([self.translate_node(v, logical_node, ctx) for v in self.hoister.hoisted(logical_node)]) # type: ignore
586
602
 
587
603
  # Nots can never hoist variables upwards
588
604
  if isinstance(parent, mm.Not):
589
605
  hoisted = ()
590
606
 
607
+ # Loops can never hoist variables upwards
608
+ if isinstance(parent, mm.Loop):
609
+ hoisted = ()
610
+ # Sequences can never hoist variables upwards
611
+ if isinstance(parent, mm.Sequence):
612
+ hoisted = ()
613
+ # Breaks can never hoist variables upwards
614
+ if isinstance(parent, mm.Break):
615
+ hoisted = ()
616
+
591
617
  # if there are no outputs, just return the logical
592
618
  outputs = self.get_outputs(children)
593
619
  if not outputs:
594
620
  return v0.Logical(
595
- engine=self.translate_reasoner(l.reasoner, l, Context.MODEL),
621
+ engine=self.translate_reasoner(logical_node.reasoner, logical_node, Context.MODEL),
596
622
  hoisted=hoisted, # type: ignore
597
623
  body=children, # type: ignore
598
- annotations=self.translate_frozen(l.annotations, l, ctx) # type: ignore
624
+ annotations=self.translate_frozen(logical_node.annotations, logical_node, ctx) # type: ignore
599
625
  )
600
626
 
601
627
 
602
628
  # if there's a main output here, we need to merge all outputs as a single one
603
- has_main_output = any(is_main_output(c) for c in l.body)
629
+ has_main_output = any(is_main_output(c) for c in logical_node.body)
604
630
  if has_main_output:
605
631
  return v0.Logical(
606
- engine=self.translate_reasoner(l.reasoner, l, Context.MODEL),
632
+ engine=self.translate_reasoner(logical_node.reasoner, logical_node, Context.MODEL),
607
633
  hoisted=hoisted, # type: ignore
608
634
  body=self._merge_outputs(children, outputs), # type: ignore
609
- annotations=self.translate_frozen(l.annotations, l, ctx) # type: ignore
635
+ annotations=self.translate_frozen(logical_node.annotations, logical_node, ctx) # type: ignore
610
636
  )
611
637
  else:
612
638
  # this logical has an output but it's not the main one; so we return the outputs
@@ -623,7 +649,7 @@ class Translator():
623
649
  # then it's possible we're filtering an outer variable, but only for this column. If
624
650
  # so, we need to alias the output and hoist it.
625
651
  # this is important because the LQP stack blows up if there's a logical with no effect
626
- if l.optional and not hoisted and not any(isinstance(c, v0.Update) for c in children):
652
+ if logical_node.optional and not hoisted and not any(isinstance(c, v0.Update) for c in children):
627
653
  # if there are no lookups, then this really is a no-op, just return outputs
628
654
  # LQP blows up with e.g. a match-only logical
629
655
  if not any(isinstance(c, v0.Lookup) for c in children):
@@ -650,6 +676,7 @@ class Translator():
650
676
  keys=output.keys,
651
677
  annotations=output.annotations
652
678
  )
679
+ self.output_column_order[new_output] = self.output_column_order.get(output, -1)
653
680
  eq = v0.Lookup(
654
681
  engine=None,
655
682
  relation=v0_builtins.eq,
@@ -667,10 +694,10 @@ class Translator():
667
694
  # return outputs + a logical with the other children
668
695
  outputs.append(
669
696
  v0.Logical(
670
- engine=self.translate_reasoner(l.reasoner, l, Context.MODEL),
697
+ engine=self.translate_reasoner(logical_node.reasoner, logical_node, Context.MODEL),
671
698
  hoisted=hoisted, # type: ignore
672
699
  body=children, # type: ignore
673
- annotations=self.translate_frozen(l.annotations, l, ctx) # type: ignore
700
+ annotations=self.translate_frozen(logical_node.annotations, logical_node, ctx) # type: ignore
674
701
  ))
675
702
  return outputs
676
703
 
@@ -718,15 +745,15 @@ class Translator():
718
745
  annotations=self.translate_frozen(w.annotations, w, ctx) # type: ignore
719
746
  )
720
747
 
721
- def translate_loop(self, l: mm.Loop, parent, ctx) -> v0.Loop:
748
+ def translate_loop(self, loop_node: mm.Loop, parent, ctx) -> v0.Loop:
722
749
  # TODO - loop is incompatible because over has multiple vars, so this is a best
723
750
  # attempt (does not matter much as this is not used in practice)
724
751
  return v0.Loop(
725
- engine=self.translate_reasoner(l.reasoner, l, Context.MODEL),
726
- hoisted=self.translate_seq(self.hoister.hoisted(l), l, ctx), # type: ignore
727
- iter=self.translate_node(l.over[0], l, ctx), # type: ignore
728
- body=self.translate_node(l.body, l, ctx), # type: ignore
729
- annotations=self.translate_frozen(l.annotations, l, ctx) # type: ignore
752
+ engine=self.translate_reasoner(loop_node.reasoner, loop_node, Context.MODEL),
753
+ hoisted=self.translate_seq(self.hoister.hoisted(loop_node), loop_node, ctx), # type: ignore
754
+ iter=self.translate_seq(loop_node.over, loop_node, ctx), # type: ignore
755
+ body=self.translate_node(loop_node.body, loop_node, ctx), # type: ignore
756
+ annotations=self.translate_frozen(loop_node.annotations, loop_node, ctx) # type: ignore
730
757
  )
731
758
 
732
759
  def translate_break(self, b: mm.Break, parent, ctx) -> v0.Break:
@@ -745,7 +772,6 @@ class Translator():
745
772
  assert isinstance(r.domain, mm.Logical)
746
773
  assert isinstance(r.check, mm.Logical)
747
774
  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):
748
- return
749
775
  v0_reqs = []
750
776
  # v0 expects a check with both the domain and the relation in it followed by the unique
751
777
  # constraint, so we construct a logical with all of that in it
@@ -754,7 +780,7 @@ class Translator():
754
780
  fields = c.args[0] # tuple of fields
755
781
  assert isinstance(fields, tuple) and all(isinstance(f, mm.Field) for f in fields)
756
782
  first_field = fields[0]
757
- if isinstance(first_field.type, mm.Table):
783
+ if isinstance(first_field.type, mm.Table): #type: ignore
758
784
  # skip union types for now
759
785
  continue
760
786
  assert isinstance(first_field, mm.Field)
@@ -779,17 +805,17 @@ class Translator():
779
805
  ))
780
806
  return v0_reqs # type: ignore
781
807
 
782
- # check = v0.Check(
783
- # check=self.translate_node(r.check, r, ctx), # type: ignore
784
- # error=self.translate_node(r.error, r, ctx) if r.error else None, # type: ignore
785
- # annotations=self.translate_frozen(r.annotations, r, ctx) # type: ignore
786
- # )
787
- # return v0.Require(
788
- # engine=self.translate_reasoner(r.reasoner, r, Context.MODEL),
789
- # domain=self.translate_node(r.domain, r, ctx), # type: ignore
790
- # checks=(check,), # type: ignore
791
- # annotations=self.translate_frozen(r.annotations, r, ctx) # type: ignore
792
- # )
808
+ check = v0.Check(
809
+ check=self.translate_node(r.check, r, ctx), # type: ignore
810
+ error=self.translate_node(r.error, r, ctx) if r.error else None, # type: ignore
811
+ annotations=self.translate_frozen(r.annotations, r, ctx) # type: ignore
812
+ )
813
+ return v0.Require(
814
+ engine=self.translate_reasoner(r.reasoner, r, Context.MODEL),
815
+ domain=self.translate_node(r.domain, r, ctx), # type: ignore
816
+ checks=(check,), # type: ignore
817
+ annotations=self.translate_frozen(r.annotations, r, ctx) # type: ignore
818
+ )
793
819
  return None
794
820
 
795
821
  # -----------------------------
@@ -823,56 +849,56 @@ class Translator():
823
849
  elif e == mm.Effect.delete:
824
850
  return v0.Effect.delete
825
851
 
826
- def translate_lookup(self, l: mm.Lookup, parent, ctx):
852
+ def translate_lookup(self, lookup_node: mm.Lookup, parent, ctx):
827
853
  # Data Node lookups
828
- if isinstance(l.relation, mm.Data):
829
- vars = [self.translate_value(l.args[0], l, ctx)]
830
- for col in l.relation.columns:
854
+ if isinstance(lookup_node.relation, mm.Data):
855
+ vars = [self.translate_value(lookup_node.args[0], lookup_node, ctx)]
856
+ for col in lookup_node.relation.columns:
831
857
  v = v0.Var(
832
858
  type=self.translate_node(col.fields[-1].type, col, Context.MODEL), # type: ignore
833
859
  name=f"{col.name}"
834
860
  )
835
861
  vars.append(v)
836
- assert isinstance(l.args[0], mm.Var)
837
- self.column_map[l.args[0]][col] = v
862
+ assert isinstance(lookup_node.args[0], mm.Var)
863
+ self.column_map[lookup_node.args[0]][col] = v
838
864
  return v0.Data(
839
- engine=self.translate_reasoner(l.reasoner, l, Context.MODEL),
840
- data=l.relation.data,
865
+ engine=self.translate_reasoner(lookup_node.reasoner, lookup_node, Context.MODEL),
866
+ data=lookup_node.relation.data,
841
867
  vars=frozen(*vars) # type: ignore
842
868
  )
843
- elif l.args and l.args[0] in self.column_map and l.relation in self.column_map[l.args[0]]: # type: ignore
844
- assert isinstance(l.args[0], mm.Var)
845
- var = self.translate_value(l.args[1], l, ctx)
846
- col_var = self.column_map[l.args[0]][l.relation] # ensure entry exists
869
+ elif lookup_node.args and lookup_node.args[0] in self.column_map and lookup_node.relation in self.column_map[lookup_node.args[0]]: # type: ignore
870
+ assert isinstance(lookup_node.args[0], mm.Var)
871
+ var = self.translate_value(lookup_node.args[1], lookup_node, ctx)
872
+ col_var = self.column_map[lookup_node.args[0]][lookup_node.relation] # ensure entry exists
847
873
  if var != col_var:
848
874
  return v0.Lookup(
849
- engine=self.translate_reasoner(l.reasoner, l, Context.MODEL),
875
+ engine=self.translate_reasoner(lookup_node.reasoner, lookup_node, Context.MODEL),
850
876
  relation=v0_builtins.eq,
851
877
  args=(var, col_var), # type: ignore
852
- annotations=self.translate_frozen(l.annotations, l, ctx) # type: ignore
878
+ annotations=self.translate_frozen(lookup_node.annotations, lookup_node, ctx) # type: ignore
853
879
  )
854
880
  # if this is a data column, we just ignore it as the data node already binds the variables
855
- elif isinstance(l.args[0].type, mm.Data):
881
+ elif isinstance(lookup_node.args[0].type, mm.Data):
856
882
  return None
857
883
  # Otherwise we keep the lookup because that's what the LQP stack expect (the lookup gets repeated for
858
884
  # each column)
859
885
 
860
886
 
861
- relation, args = self._resolve_reading(l, ctx)
887
+ relation, args = self._resolve_reading(lookup_node, ctx)
862
888
  if relation is None:
863
889
  return None
864
890
 
865
891
 
866
892
  # Specific rewrites
867
- rewrite = self.rewrite_lookup(l, parent, ctx)
893
+ rewrite = self.rewrite_lookup(lookup_node, parent, ctx)
868
894
  if rewrite is None:
869
- args = self.rewrite_cdc_args(l, args, parent, ctx)
895
+ args = self.rewrite_cdc_args(lookup_node, args, parent, ctx)
870
896
  # General translation
871
897
  rewrite = v0.Lookup(
872
- engine=self.translate_reasoner(l.reasoner, l, Context.MODEL),
898
+ engine=self.translate_reasoner(lookup_node.reasoner, lookup_node, Context.MODEL),
873
899
  relation=relation, # type: ignore
874
900
  args=args,
875
- annotations=self.translate_frozen(l.annotations, l, ctx) # type: ignore
901
+ annotations=self.translate_frozen(lookup_node.annotations, lookup_node, ctx) # type: ignore
876
902
  )
877
903
 
878
904
  return rewrite
@@ -910,8 +936,10 @@ class Translator():
910
936
  annotations=self.translate_frozen(u.annotations, u, ctx) # type: ignore
911
937
  )
912
938
  table = u.relation.fields[0].type
913
- if isinstance(table, mm.Table) and not table.uri.startswith("dataframe://"):
914
- self.exports.add(out)
939
+ assert isinstance(table, mm.Table)
940
+ if not table.uri.startswith("dataframe://"):
941
+ self.exports[out] = table
942
+ self.output_column_order[out] = table.columns.index(u.relation)
915
943
  return out
916
944
  else:
917
945
  relation, args = self._resolve_reading(u, ctx)
@@ -943,14 +971,14 @@ class Translator():
943
971
 
944
972
  # hoist the return variables (those that are not inputs to the aggregation)
945
973
  return_args = []
946
- for field, arg in zip(a.aggregation.fields, a.args):
947
- if not field.input:
974
+ for fld, arg in zip(a.aggregation.fields, a.args):
975
+ if not fld.input:
948
976
  return_args.append(arg)
949
977
  hoisted=self.translate_seq(return_args, a, ctx)
950
978
 
951
979
  # body must contain the aggregate body plus the aggregate itself
952
980
  body = self.translate_node(a.body, a, ctx)
953
- body = list(body.body if isinstance(body, v0.Logical) else [body])
981
+ body = [body]
954
982
 
955
983
  # construct the aggregate node
956
984
  engine = self.translate_reasoner(a.reasoner, a, Context.MODEL)
@@ -964,13 +992,13 @@ class Translator():
964
992
  if a.aggregation == b.aggregates.rank:
965
993
  assert isinstance(args[0], tuple)
966
994
  assert isinstance(args[1], tuple)
967
- v0_agg = v0.Rank(engine, projection, group, args[0], tuple(l.value for l in args[1]), args[-1], 0, annotations,) # type: ignore
995
+ v0_agg = v0.Rank(engine, projection, group, args[0], tuple(lit.value for lit in args[1]), args[-1], 0, annotations,) # type: ignore
968
996
  elif a.aggregation == b.aggregates.limit:
969
997
  assert isinstance(args[0], v0.Literal)
970
998
  assert isinstance(args[1], tuple)
971
999
  assert isinstance(args[2], tuple)
972
1000
  result = v0.Var(type=v0_types.Int64, name="limit_result")
973
- v0_agg = v0.Rank(engine, projection, group, args[1], tuple(l.value for l in args[2]), result, args[0].value, annotations,) # type: ignore
1001
+ v0_agg = v0.Rank(engine, projection, group, args[1], tuple(lit.value for lit in args[2]), result, args[0].value, annotations,) # type: ignore
974
1002
  else:
975
1003
  v0_agg = v0.Aggregate(engine, aggregation, projection, group, args, annotations) # type: ignore
976
1004
 
@@ -1077,7 +1105,7 @@ class Translator():
1077
1105
  # Library-specific rewrites
1078
1106
  # -----------------------------------------------------------------------------
1079
1107
 
1080
- def adjust_index(self, l: mm.Lookup, indices: list[int], ctx, args=None, no_outputs=False):
1108
+ def adjust_index(self, lookup_node: mm.Lookup, indices: list[int], ctx, args=None, no_outputs=False):
1081
1109
  """ Rewrite the lookup such that the args at `indices` are adjusted to convert from
1082
1110
  0-based indexing (as in the mm) to 1-based indexing (as in v0's backend).
1083
1111
 
@@ -1085,16 +1113,16 @@ class Translator():
1085
1113
  it to the lookup. If the field is output, we create a tmp var to hold the result
1086
1114
  of the lookup, and then create a subsequent task to decrement the tmp var by 1.
1087
1115
 
1088
- `args` can be used to provide a different set of args than l.args, in case they need
1116
+ `args` can be used to provide a different set of args than lookup_node.args, in case they need
1089
1117
  reordering (e.g. strings.split).
1090
1118
  """
1091
1119
  if args is None:
1092
- args = l.args
1120
+ args = lookup_node.args
1093
1121
  # vars to hold the result from the v0 lookup
1094
1122
  tmps = {}
1095
1123
  for i in indices:
1096
- index_type = l.relation.fields[i].type
1097
- tmps[i] = mm.Var(type=index_type, name=f"tmp")
1124
+ index_type = lookup_node.relation.fields[i].type
1125
+ tmps[i] = mm.Var(type=index_type, name="tmp")
1098
1126
  # insert tmp at index
1099
1127
  replaced_args = []
1100
1128
  for i, arg in enumerate(args):
@@ -1103,37 +1131,37 @@ class Translator():
1103
1131
  else:
1104
1132
  replaced_args.append(arg)
1105
1133
  # translate the lookup
1106
- args = tuple(self.translate_value(arg, l, ctx) for arg in replaced_args)
1107
- args = self.rewrite_cdc_args(l, args, l, ctx)
1134
+ args = tuple(self.translate_value(arg, lookup_node, ctx) for arg in replaced_args)
1135
+ args = self.rewrite_cdc_args(lookup_node, args, lookup_node, ctx)
1108
1136
  lookup = v0.Lookup(
1109
- engine=self.translate_reasoner(l.reasoner, l, Context.MODEL),
1110
- relation=self.translate_node(l.relation), # type: ignore
1137
+ engine=self.translate_reasoner(lookup_node.reasoner, lookup_node, Context.MODEL),
1138
+ relation=self.translate_node(lookup_node.relation), # type: ignore
1111
1139
  args=args,
1112
- annotations=self.translate_frozen(l.annotations, l, ctx) # type: ignore
1140
+ annotations=self.translate_frozen(lookup_node.annotations, lookup_node, ctx) # type: ignore
1113
1141
  )
1114
1142
  # subtract 1 from the index to convert from 1-based to 0-based
1115
1143
  tasks = []
1116
1144
  for index in indices:
1117
- arg = args[index]
1145
+ arg = lookup_node.args[index]
1118
1146
  if isinstance(arg, mm.Literal):
1119
- if l.relation.fields[index].input:
1147
+ if lookup_node.relation.fields[index].input:
1120
1148
  new = mm.Literal(arg.type, arg.value + 1) # type: ignore
1121
1149
  else:
1122
1150
  new = mm.Literal(arg.type, arg.value + 1) # type: ignore
1123
1151
  tasks.append(v0.Lookup(
1124
1152
  engine=None,
1125
- relation=v0_builtins.eq, # type: ignore
1153
+ relation=v0_builtins.eq,
1126
1154
  args=(
1127
1155
  self.translate_value(tmps[index], lookup, ctx),
1128
1156
  self.translate_value(new, lookup, ctx),
1129
1157
  )
1130
1158
  ))
1131
- elif l.relation.fields[index].input:
1159
+ elif lookup_node.relation.fields[index].input:
1132
1160
  tasks.append(v0.Lookup(
1133
1161
  engine=None,
1134
- relation=v0_builtins.plus, # type: ignore
1162
+ relation=v0_builtins.plus,
1135
1163
  args=(
1136
- self.translate_value(args[index], lookup, ctx),
1164
+ self.translate_value(arg, lookup, ctx),
1137
1165
  v0.Literal(v0_types.Int128, 1),
1138
1166
  self.translate_value(tmps[index], lookup, ctx),
1139
1167
  ),
@@ -1146,7 +1174,7 @@ class Translator():
1146
1174
  args=(
1147
1175
  self.translate_value(tmps[index], lookup, ctx),
1148
1176
  v0.Literal(v0_types.Int128, 1),
1149
- self.translate_value(args[index], lookup, ctx),
1177
+ self.translate_value(arg, lookup, ctx),
1150
1178
  ),
1151
1179
  )
1152
1180
  )
@@ -1155,37 +1183,37 @@ class Translator():
1155
1183
 
1156
1184
  return tasks
1157
1185
 
1158
- def decrement(self, l: mm.Lookup, index: int, ctx):
1186
+ def decrement(self, lookup_node: mm.Lookup, index: int, ctx):
1159
1187
  """ Rewrite the lookup such that the arg at `index` is decremented by 1 before the
1160
1188
  lookup. """
1161
- x = l.args[index]
1189
+ x = lookup_node.args[index]
1162
1190
  if isinstance(x, mm.Literal):
1163
1191
  # if the arg is a literal, just decrement the literal directly
1164
1192
  new_literal = mm.Literal(type=x.type, value=x.value - 1) # type: ignore
1165
1193
  return v0.Lookup(
1166
1194
  engine=None,
1167
- relation=self.translate_node(l.relation), # type: ignore
1168
- args=tuple(self.translate_value(arg, l, ctx) if i != index else self.translate_value(new_literal, l, ctx) for i, arg in enumerate(l.args)),
1169
- annotations=self.translate_frozen(l.annotations, l, ctx) # type: ignore
1195
+ relation=self.translate_node(lookup_node.relation), # type: ignore
1196
+ args=tuple(self.translate_value(arg, lookup_node, ctx) if i != index else self.translate_value(new_literal, lookup_node, ctx) for i, arg in enumerate(lookup_node.args)),
1197
+ annotations=self.translate_frozen(lookup_node.annotations, lookup_node, ctx) # type: ignore
1170
1198
  )
1171
1199
 
1172
1200
  # arg is not a literal, so we need to create a tmp var to store the decremented value
1173
- tmp = self.translate_value(mm.Var(type=b.core.Number, name="tmp"), l, ctx)
1201
+ tmp = self.translate_value(mm.Var(type=b.core.Number, name="tmp"), lookup_node, ctx)
1174
1202
  # lookup(..., tmp, ...)
1175
1203
  new = v0.Lookup(
1176
1204
  engine=None,
1177
- relation=self.translate_node(l.relation), # type: ignore
1178
- args=tuple(self.translate_value(arg, l, ctx) if i != index else tmp for i, arg in enumerate(l.args)),
1179
- annotations=self.translate_frozen(l.annotations, l, ctx) # type: ignore
1205
+ relation=self.translate_node(lookup_node.relation), # type: ignore
1206
+ args=tuple(self.translate_value(arg, lookup_node, ctx) if i != index else tmp for i, arg in enumerate(lookup_node.args)),
1207
+ annotations=self.translate_frozen(lookup_node.annotations, lookup_node, ctx) # type: ignore
1180
1208
  )
1181
1209
  casted = self.cast(new, ctx)
1182
1210
  return (
1183
- # tmp = l.args[index] - 1
1211
+ # tmp = lookup_node.args[index] - 1
1184
1212
  v0.Lookup(
1185
1213
  engine=None,
1186
1214
  relation=v0_builtins.minus, # type: ignore
1187
1215
  args=(
1188
- self.translate_value(l.args[index], l, ctx),
1216
+ self.translate_value(lookup_node.args[index], lookup_node, ctx),
1189
1217
  v0.Literal(v0_types.Int128, 1),
1190
1218
  tmp,
1191
1219
  ),
@@ -1193,31 +1221,34 @@ class Translator():
1193
1221
  *casted
1194
1222
  )
1195
1223
 
1196
- def cast(self, l: v0.Lookup, ctx, no_outputs=False):
1224
+ def cast(self, lookup_node: v0.Lookup, ctx, no_outputs=False):
1197
1225
  # note that everything here is in v0
1198
1226
  inputs = []
1199
1227
  outputs = []
1200
1228
  args = []
1201
- fields = l.relation.fields
1229
+ fields = lookup_node.relation.fields
1202
1230
  # if there are overloads, we need to cast based on the most compatible overload
1203
- if l.relation.overloads:
1231
+ if lookup_node.relation.overloads:
1204
1232
  inf = float("inf")
1205
1233
  min_cost = inf
1206
- for overload in l.relation.overloads:
1234
+ for overload in lookup_node.relation.overloads:
1207
1235
  total = 0
1208
- for arg, field in zip(l.args, overload.fields):
1236
+ for arg, field in zip(lookup_node.args, overload.fields):
1209
1237
  if v0_typer.to_type(arg) != field.type:
1210
1238
  total += 1
1211
1239
  if total < min_cost:
1212
1240
  min_cost = total
1213
1241
  fields = overload.fields
1214
1242
 
1215
- for arg, field in zip(l.args, fields):
1243
+ for arg, field in zip(lookup_node.args, fields):
1216
1244
  target_type = field.type
1217
1245
  if target_type is None or not isinstance(arg, (v0.Var, v0.Literal)) or arg.type == target_type:
1218
1246
  args.append(arg)
1219
1247
  continue
1220
- if field.input:
1248
+ if isinstance(arg, v0.Literal):
1249
+ # for literals, we can just create a new literal with the target type
1250
+ args.append(v0.Literal(type=target_type, value=arg.value))
1251
+ elif field.input:
1221
1252
  cast_var = v0.Var(type=target_type, name="cast_var")
1222
1253
  args.append(cast_var)
1223
1254
  inputs.append(v0.Lookup(
@@ -1241,16 +1272,16 @@ class Translator():
1241
1272
  args.append(arg)
1242
1273
 
1243
1274
  lookup = v0.Lookup(
1244
- engine=l.engine,
1245
- relation=l.relation,
1275
+ engine=lookup_node.engine,
1276
+ relation=lookup_node.relation,
1246
1277
  args=tuple(args),
1247
- annotations=l.annotations
1278
+ annotations=lookup_node.annotations
1248
1279
  )
1249
1280
  if no_outputs:
1250
1281
  outputs = []
1251
1282
  return [*inputs, lookup, *outputs]
1252
1283
 
1253
- def rewrite_lookup(self, l: mm.Lookup, parent, ctx):
1284
+ def rewrite_lookup(self, lookup_node: mm.Lookup, parent, ctx):
1254
1285
  """ Special cases for v1 builtins that either have some different shape in v0 or
1255
1286
  do not exist as a primitive in the engine, so we want to compose the result so that
1256
1287
  LQP works. Return None if no rewrite is needed.
@@ -1263,113 +1294,113 @@ class Translator():
1263
1294
  # -----------------------------------------------------------------------------
1264
1295
  # Common
1265
1296
  # -----------------------------------------------------------------------------
1266
- if l.relation == b.common.range:
1297
+ if lookup_node.relation == b.common.range:
1267
1298
  # python/PyRel range has inclusive start but exclusive stop, whereas LQP/Rel has
1268
1299
  # inclusive stop, so we have to decrement the stop index by 1
1269
- # tmp, lookup = self.decrement(l, 1, ctx)
1300
+ # tmp, lookup = self.decrement(lookup_node, 1, ctx)
1270
1301
  # return [tmp] + self.cast(lookup, [v0_types.Int64, v0_types.Int64, v0_types.Int64, v0_types.Int64], ctx)
1271
- return self.decrement(l, 1, ctx)
1302
+ return self.decrement(lookup_node, 1, ctx)
1272
1303
 
1273
- elif l.relation == b.core.cast:
1304
+ elif lookup_node.relation == b.core.cast:
1274
1305
  return v0.Lookup(
1275
- engine=self.translate_reasoner(l.reasoner, l, Context.TASK),
1306
+ engine=self.translate_reasoner(lookup_node.reasoner, lookup_node, Context.TASK),
1276
1307
  relation=v0_builtins.builtin_relations_by_name["cast"], # type: ignore
1277
1308
  args=(
1278
- self.translate_value(l.args[0], l, Context.MODEL),
1279
- self.translate_value(l.args[1], l, Context.TASK),
1280
- self.translate_value(l.args[2], l, Context.TASK)
1309
+ self.translate_value(lookup_node.args[0], lookup_node, Context.MODEL),
1310
+ self.translate_value(lookup_node.args[1], lookup_node, Context.TASK),
1311
+ self.translate_value(lookup_node.args[2], lookup_node, Context.TASK)
1281
1312
  ),
1282
- annotations=self.translate_frozen(l.annotations, l, ctx) # type: ignore
1313
+ annotations=self.translate_frozen(lookup_node.annotations, lookup_node, ctx) # type: ignore
1283
1314
  )
1284
1315
 
1285
1316
  # -----------------------------------------------------------------------------
1286
1317
  # Strings
1287
1318
  # -----------------------------------------------------------------------------
1288
1319
 
1289
- elif l.relation == b.strings.len:
1320
+ elif lookup_node.relation == b.strings.len:
1290
1321
  lookup = v0.Lookup(
1291
- engine=self.translate_reasoner(l.reasoner, l, Context.MODEL),
1322
+ engine=self.translate_reasoner(lookup_node.reasoner, lookup_node, Context.MODEL),
1292
1323
  relation=v0_builtins.builtin_relations_by_name["num_chars"], # type: ignore
1293
1324
  args=(
1294
- self.translate_value(l.args[0], l, ctx),
1295
- self.translate_value(l.args[1], l, ctx),
1325
+ self.translate_value(lookup_node.args[0], lookup_node, ctx),
1326
+ self.translate_value(lookup_node.args[1], lookup_node, ctx),
1296
1327
  ),
1297
- annotations=self.translate_frozen(l.annotations, l, ctx) # type: ignore
1328
+ annotations=self.translate_frozen(lookup_node.annotations, lookup_node, ctx) # type: ignore
1298
1329
  )
1299
1330
  return self.cast(lookup, ctx)
1300
1331
 
1301
- elif l.relation == b.strings.split:
1332
+ elif lookup_node.relation == b.strings.split:
1302
1333
  # swap first two args because that's how rel and v0 expect them
1303
- args = l.args[1], l.args[0], l.args[2], l.args[3]
1304
- return self.adjust_index(l, [2], ctx, args=args)
1334
+ args = lookup_node.args[1], lookup_node.args[0], lookup_node.args[2], lookup_node.args[3]
1335
+ return self.adjust_index(lookup_node, [2], ctx, args=args)
1305
1336
 
1306
- elif l.relation == b.strings.substring:
1307
- return self.adjust_index(l, [1], ctx)
1337
+ elif lookup_node.relation == b.strings.substring:
1338
+ return self.adjust_index(lookup_node, [1], ctx)
1308
1339
 
1309
1340
  # -----------------------------------------------------------------------------
1310
1341
  # RE
1311
1342
  # -----------------------------------------------------------------------------
1312
- elif l.relation == b.re.regex_match_all:
1313
- return self.adjust_index(l, [2], ctx)
1343
+ elif lookup_node.relation == b.re.regex_match_all:
1344
+ return self.adjust_index(lookup_node, [2], ctx)
1314
1345
 
1315
1346
  # -----------------------------------------------------------------------------
1316
1347
  # Math
1317
1348
  # -----------------------------------------------------------------------------
1318
- elif l.relation == b.math.degrees:
1349
+ elif lookup_node.relation == b.math.degrees:
1319
1350
  # degrees(radians, x) = /(radians, (pi/180.0), x)
1320
1351
  return v0.Lookup(
1321
- engine=self.translate_reasoner(l.reasoner, l, Context.MODEL),
1352
+ engine=self.translate_reasoner(lookup_node.reasoner, lookup_node, Context.MODEL),
1322
1353
  relation=v0_builtins.builtin_relations_by_name["/"], # type: ignore
1323
1354
  args=(
1324
- self.translate_value(l.args[0], l, ctx),
1355
+ self.translate_value(lookup_node.args[0], lookup_node, ctx),
1325
1356
  v0.Literal(v0_types.Float, pi / 180.0),
1326
- self.translate_value(l.args[1], l, ctx)
1357
+ self.translate_value(lookup_node.args[1], lookup_node, ctx)
1327
1358
  ),
1328
- annotations=self.translate_frozen(l.annotations, l, ctx) # type: ignore
1359
+ annotations=self.translate_frozen(lookup_node.annotations, lookup_node, ctx) # type: ignore
1329
1360
  )
1330
- elif l.relation == b.math.radians:
1361
+ elif lookup_node.relation == b.math.radians:
1331
1362
  # radians(degrees, x) = /(degrees, (180.0/pi), x)
1332
1363
  return v0.Lookup(
1333
- engine=self.translate_reasoner(l.reasoner, l, Context.MODEL),
1364
+ engine=self.translate_reasoner(lookup_node.reasoner, lookup_node, Context.MODEL),
1334
1365
  relation=v0_builtins.builtin_relations_by_name["/"], # type: ignore
1335
1366
  args=(
1336
- self.translate_value(l.args[0], l, ctx),
1367
+ self.translate_value(lookup_node.args[0], lookup_node, ctx),
1337
1368
  v0.Literal(v0_types.Float, 180.0 / pi),
1338
- self.translate_value(l.args[1], l, ctx)
1369
+ self.translate_value(lookup_node.args[1], lookup_node, ctx)
1339
1370
  ),
1340
- annotations=self.translate_frozen(l.annotations, l, ctx) # type: ignore
1371
+ annotations=self.translate_frozen(lookup_node.annotations, lookup_node, ctx) # type: ignore
1341
1372
  )
1342
1373
 
1343
1374
  # -----------------------------------------------------------------------------
1344
1375
  # Datetime
1345
1376
  # -----------------------------------------------------------------------------
1346
1377
  #
1347
- # elif l.relation == b.datetime.date_range:
1348
- # return self.rewrite_date_range(l, parent, ctx)
1349
- # elif l.relation == b.datetime.datetime_range:
1378
+ # elif lookup_node.relation == b.datetime.date_range:
1379
+ # return self.rewrite_date_range(lookup_node, parent, ctx)
1380
+ # elif lookup_node.relation == b.datetime.datetime_range:
1350
1381
  # pass
1351
1382
 
1352
- elif l.relation == b.datetime.date_add:
1383
+ elif lookup_node.relation == b.datetime.date_add:
1353
1384
  return v0.Lookup(
1354
- engine=self.translate_reasoner(l.reasoner, l, Context.MODEL),
1355
- relation=self.translate_relation(l.relation, l, ctx), # type: ignore
1356
- args=self.translate_seq(l.args, l, ctx), # type: ignore
1357
- annotations=self.translate_frozen(l.annotations, l, ctx) # type: ignore
1385
+ engine=self.translate_reasoner(lookup_node.reasoner, lookup_node, Context.MODEL),
1386
+ relation=self.translate_relation(lookup_node.relation, lookup_node, ctx), # type: ignore
1387
+ args=self.translate_seq(lookup_node.args, lookup_node, ctx), # type: ignore
1388
+ annotations=self.translate_frozen(lookup_node.annotations, lookup_node, ctx) # type: ignore
1358
1389
  )
1359
1390
 
1360
- if b.datetime[l.relation.name] is l.relation:
1361
- return self.adjust_index(l, [], ctx, no_outputs=True)
1391
+ if b.datetime[lookup_node.relation.name] is lookup_node.relation:
1392
+ return self.adjust_index(lookup_node, [], ctx, no_outputs=True)
1362
1393
 
1363
- v0_rel = self.translate_node(l.relation, l, ctx)
1394
+ v0_rel = self.translate_node(lookup_node.relation, lookup_node, ctx)
1364
1395
  if isinstance(v0_rel, v0.Relation) and any(f.type == v0_types.Int64 for f in v0_rel.fields):
1365
- return self.adjust_index(l, [], ctx)
1396
+ return self.adjust_index(lookup_node, [], ctx)
1366
1397
 
1367
1398
  return None
1368
1399
 
1369
1400
 
1370
1401
  # TODO - Currently implemented in the std.datetime itself.
1371
1402
  # def rewrite_date_range(self, l: mm.Lookup, parent, ctx: Context):
1372
- # start, end, freq, date = l.args
1403
+ # start, end, freq, date = lookup_node.args
1373
1404
  # result = []
1374
1405
 
1375
1406
  # range_end = num_days = mm.Var(type=b.core.Number, name="num_days")