pytrilogy 0.0.1.118__py3-none-any.whl → 0.0.2.2__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.

Potentially problematic release.


This version of pytrilogy might be problematic. Click here for more details.

Files changed (45) hide show
  1. {pytrilogy-0.0.1.118.dist-info → pytrilogy-0.0.2.2.dist-info}/METADATA +1 -1
  2. pytrilogy-0.0.2.2.dist-info/RECORD +82 -0
  3. {pytrilogy-0.0.1.118.dist-info → pytrilogy-0.0.2.2.dist-info}/WHEEL +1 -1
  4. trilogy/__init__.py +1 -1
  5. trilogy/constants.py +6 -0
  6. trilogy/core/enums.py +7 -2
  7. trilogy/core/env_processor.py +43 -19
  8. trilogy/core/functions.py +1 -0
  9. trilogy/core/models.py +674 -146
  10. trilogy/core/optimization.py +31 -28
  11. trilogy/core/optimizations/inline_constant.py +4 -1
  12. trilogy/core/optimizations/inline_datasource.py +25 -4
  13. trilogy/core/optimizations/predicate_pushdown.py +94 -54
  14. trilogy/core/processing/concept_strategies_v3.py +69 -39
  15. trilogy/core/processing/graph_utils.py +3 -3
  16. trilogy/core/processing/node_generators/__init__.py +0 -2
  17. trilogy/core/processing/node_generators/basic_node.py +30 -17
  18. trilogy/core/processing/node_generators/filter_node.py +3 -1
  19. trilogy/core/processing/node_generators/node_merge_node.py +345 -96
  20. trilogy/core/processing/node_generators/rowset_node.py +18 -16
  21. trilogy/core/processing/node_generators/select_node.py +45 -85
  22. trilogy/core/processing/nodes/__init__.py +2 -0
  23. trilogy/core/processing/nodes/base_node.py +22 -5
  24. trilogy/core/processing/nodes/filter_node.py +3 -0
  25. trilogy/core/processing/nodes/group_node.py +20 -2
  26. trilogy/core/processing/nodes/merge_node.py +32 -18
  27. trilogy/core/processing/nodes/select_node_v2.py +17 -3
  28. trilogy/core/processing/utility.py +100 -8
  29. trilogy/core/query_processor.py +77 -24
  30. trilogy/dialect/base.py +11 -46
  31. trilogy/dialect/bigquery.py +1 -1
  32. trilogy/dialect/common.py +11 -0
  33. trilogy/dialect/duckdb.py +1 -1
  34. trilogy/dialect/presto.py +1 -0
  35. trilogy/hooks/graph_hook.py +50 -5
  36. trilogy/hooks/query_debugger.py +1 -0
  37. trilogy/parsing/common.py +8 -5
  38. trilogy/parsing/parse_engine.py +52 -27
  39. trilogy/parsing/render.py +20 -9
  40. trilogy/parsing/trilogy.lark +13 -8
  41. pytrilogy-0.0.1.118.dist-info/RECORD +0 -83
  42. trilogy/core/processing/node_generators/concept_merge_node.py +0 -214
  43. {pytrilogy-0.0.1.118.dist-info → pytrilogy-0.0.2.2.dist-info}/LICENSE.md +0 -0
  44. {pytrilogy-0.0.1.118.dist-info → pytrilogy-0.0.2.2.dist-info}/entry_points.txt +0 -0
  45. {pytrilogy-0.0.1.118.dist-info → pytrilogy-0.0.2.2.dist-info}/top_level.txt +0 -0
@@ -32,6 +32,7 @@ from trilogy.core.enums import (
32
32
  WindowType,
33
33
  DatePart,
34
34
  ShowCategory,
35
+ SelectFiltering,
35
36
  )
36
37
  from trilogy.core.exceptions import InvalidSyntaxException, UndefinedConceptException
37
38
  from trilogy.core.functions import (
@@ -68,7 +69,7 @@ from trilogy.core.models import (
68
69
  ConceptTransform,
69
70
  Conditional,
70
71
  Datasource,
71
- MergeStatement,
72
+ MergeStatementV2,
72
73
  Environment,
73
74
  FilterItem,
74
75
  Function,
@@ -400,7 +401,7 @@ class ParseToObjects(Transformer):
400
401
  @v_args(meta=True)
401
402
  def concept_property_declaration(self, meta: Meta, args) -> Concept:
402
403
 
403
- metadata = None
404
+ metadata = Metadata()
404
405
  modifiers = []
405
406
  for arg in args:
406
407
  if isinstance(arg, Metadata):
@@ -439,7 +440,7 @@ class ParseToObjects(Transformer):
439
440
 
440
441
  @v_args(meta=True)
441
442
  def concept_declaration(self, meta: Meta, args) -> ConceptDeclarationStatement:
442
- metadata = None
443
+ metadata = Metadata()
443
444
  modifiers = []
444
445
  for arg in args:
445
446
  if isinstance(arg, Metadata):
@@ -447,9 +448,7 @@ class ParseToObjects(Transformer):
447
448
  if isinstance(arg, Modifier):
448
449
  modifiers.append(arg)
449
450
  name = args[1]
450
- lookup, namespace, name, parent = parse_concept_reference(
451
- name, self.environment
452
- )
451
+ _, namespace, name, _ = parse_concept_reference(name, self.environment)
453
452
  concept = Concept(
454
453
  name=name,
455
454
  datatype=args[2],
@@ -606,6 +605,7 @@ class ParseToObjects(Transformer):
606
605
  columns: List[ColumnAssignment] = args[1]
607
606
  grain: Optional[Grain] = None
608
607
  address: Optional[Address] = None
608
+ where: Optional[WhereClause] = None
609
609
  for val in args[1:]:
610
610
  if isinstance(val, Address):
611
611
  address = val
@@ -613,6 +613,8 @@ class ParseToObjects(Transformer):
613
613
  grain = val
614
614
  elif isinstance(val, Query):
615
615
  address = Address(location=f"({val.text})", is_query=True)
616
+ elif isinstance(val, WhereClause):
617
+ where = val
616
618
  if not address:
617
619
  raise ValueError(
618
620
  "Malformed datasource, missing address or query declaration"
@@ -625,6 +627,7 @@ class ParseToObjects(Transformer):
625
627
  grain=grain, # type: ignore
626
628
  address=address,
627
629
  namespace=self.environment.namespace,
630
+ where=where,
628
631
  )
629
632
  for column in columns:
630
633
  column.concept = column.concept.with_grain(datasource.grain)
@@ -754,19 +757,21 @@ class ParseToObjects(Transformer):
754
757
  return [x for x in args]
755
758
 
756
759
  @v_args(meta=True)
757
- def merge_statement(self, meta: Meta, args) -> MergeStatement:
758
- parsed = [self.environment.concepts[x] for x in args]
759
- datatypes = {x.datatype for x in parsed}
760
- if not len(datatypes) == 1 and self.environment.concepts.fail_on_missing:
761
- type_dict = {x.address: x.datatype for x in parsed}
762
- raise SyntaxError(
763
- f"Cannot merge concepts with different datatype"
764
- f"line: {meta.line} concepts: {type_dict}"
765
- )
766
- merge = MergeStatement(concepts=parsed, datatype=datatypes.pop())
767
- new = merge.merge_concept
768
- self.environment.add_concept(new, meta=meta)
769
- return merge
760
+ def merge_statement_v2(self, meta: Meta, args) -> MergeStatementV2:
761
+ modifiers = []
762
+ concepts = []
763
+ for arg in args:
764
+ if isinstance(arg, Modifier):
765
+ modifiers.append(arg)
766
+ else:
767
+ concepts.append(self.environment.concepts[arg])
768
+ new = MergeStatementV2(
769
+ source=concepts[0], target=concepts[1], modifiers=modifiers
770
+ )
771
+
772
+ self.environment.merge_concept(new.source, new.target, modifiers)
773
+
774
+ return new
770
775
 
771
776
  @v_args(meta=True)
772
777
  def rawsql_statement(self, meta: Meta, args) -> RawSQLStatement:
@@ -892,7 +897,7 @@ class ParseToObjects(Transformer):
892
897
 
893
898
  @v_args(meta=True)
894
899
  def select_statement(self, meta: Meta, args) -> SelectStatement:
895
- select_items = None
900
+ select_items: List[SelectItem] | None = None
896
901
  limit = None
897
902
  order_by = None
898
903
  where = None
@@ -919,16 +924,24 @@ class ParseToObjects(Transformer):
919
924
  # so rebuild at this point in the tree
920
925
  # TODO: simplify
921
926
  if isinstance(item.content, ConceptTransform):
922
- new_concept = item.content.output.with_select_grain(output.grain)
927
+ new_concept = item.content.output.with_select_context(
928
+ output.grain,
929
+ conditional=(
930
+ output.where_clause.conditional
931
+ if output.where_clause
932
+ and output.where_clause_category == SelectFiltering.IMPLICIT
933
+ else None
934
+ ),
935
+ )
923
936
  self.environment.add_concept(new_concept, meta=meta)
924
937
  item.content.output = new_concept
925
938
  if order_by:
926
- for item in order_by.items:
939
+ for orderitem in order_by.items:
927
940
  if (
928
- isinstance(item.expr, Concept)
929
- and item.expr.purpose == Purpose.METRIC
941
+ isinstance(orderitem.expr, Concept)
942
+ and orderitem.expr.purpose == Purpose.METRIC
930
943
  ):
931
- item.expr = item.expr.with_grain(output.grain)
944
+ orderitem.expr = orderitem.expr.with_grain(output.grain)
932
945
  return output
933
946
 
934
947
  @v_args(meta=True)
@@ -1045,8 +1058,20 @@ class ParseToObjects(Transformer):
1045
1058
  def parenthetical(self, args):
1046
1059
  return Parenthetical(content=args[0])
1047
1060
 
1061
+ def condition_parenthetical(self, args):
1062
+ return Parenthetical(content=args[0])
1063
+
1048
1064
  def conditional(self, args):
1049
- return Conditional(left=args[0], right=args[2], operator=args[1])
1065
+ def munch_args(args):
1066
+ while args:
1067
+ if len(args) == 1:
1068
+ return args[0]
1069
+ else:
1070
+ return Conditional(
1071
+ left=args[0], operator=args[1], right=munch_args(args[2:])
1072
+ )
1073
+
1074
+ return munch_args(args)
1050
1075
 
1051
1076
  def window_order(self, args):
1052
1077
  return WindowOrder(args[0])
@@ -1723,7 +1748,7 @@ def parse_text(text: str, environment: Optional[Environment] = None) -> Tuple[
1723
1748
  | None
1724
1749
  ],
1725
1750
  ]:
1726
- environment = environment or Environment(datasources={})
1751
+ environment = environment or Environment()
1727
1752
  parser = ParseToObjects(visit_tokens=True, text=text, environment=environment)
1728
1753
 
1729
1754
  try:
trilogy/parsing/render.py CHANGED
@@ -33,13 +33,13 @@ from trilogy.core.models import (
33
33
  PersistStatement,
34
34
  ListWrapper,
35
35
  RowsetDerivationStatement,
36
- MergeStatement,
37
36
  MultiSelectStatement,
38
37
  OrderBy,
39
38
  AlignClause,
40
39
  AlignItem,
41
40
  RawSQLStatement,
42
41
  NumericType,
42
+ MergeStatementV2,
43
43
  )
44
44
  from trilogy.core.enums import Modifier
45
45
 
@@ -131,22 +131,21 @@ class Renderer:
131
131
  @to_string.register
132
132
  def _(self, arg: Datasource):
133
133
  assignments = ",\n\t".join([self.to_string(x) for x in arg.columns])
134
- return f"""datasource {arg.name} (
134
+ base = f"""datasource {arg.name} (
135
135
  {assignments}
136
136
  )
137
137
  {self.to_string(arg.grain)}
138
- {self.to_string(arg.address)};"""
138
+ {self.to_string(arg.address)}"""
139
+ if arg.where:
140
+ base += f"\n{self.to_string(arg.where)}"
141
+ base += ";"
142
+ return base
139
143
 
140
144
  @to_string.register
141
145
  def _(self, arg: "Grain"):
142
146
  components = ",".join(self.to_string(x) for x in arg.components)
143
147
  return f"grain ({components})"
144
148
 
145
- @to_string.register
146
- def _(self, arg: MergeStatement):
147
- components = ", ".join(self.to_string(x) for x in arg.concepts)
148
- return f"merge {components};"
149
-
150
149
  @to_string.register
151
150
  def _(self, arg: "Query"):
152
151
  return f"""query {arg.text}"""
@@ -201,7 +200,7 @@ class Renderer:
201
200
 
202
201
  @to_string.register
203
202
  def _(self, arg: "ColumnAssignment"):
204
- return f"{arg.alias}:{self.to_string(arg.concept)}"
203
+ return f"{arg.alias}: {self.to_string(arg.concept)}"
205
204
 
206
205
  @to_string.register
207
206
  def _(self, arg: "ConceptDeclarationStatement"):
@@ -341,6 +340,18 @@ class Renderer:
341
340
  return f"{self.to_string(arg.function)} by {self.to_string(arg.by)}"
342
341
  return f"{self.to_string(arg.function)}"
343
342
 
343
+ @to_string.register
344
+ def _(self, arg: MergeStatementV2):
345
+ return f"MERGE {self.to_string(arg.source)} into {''.join([self.to_string(modifier) for modifier in arg.modifiers])}{self.to_string(arg.target)};"
346
+
347
+ @to_string.register
348
+ def _(self, arg: Modifier):
349
+ if arg == Modifier.PARTIAL:
350
+ return "~"
351
+ if arg == Modifier.HIDDEN:
352
+ return "--"
353
+ return arg.value
354
+
344
355
  @to_string.register
345
356
  def _(self, arg: int):
346
357
  return f"{arg}"
@@ -8,7 +8,8 @@
8
8
  | persist_statement
9
9
  | rowset_derivation_statement
10
10
  | import_statement
11
- | merge_statement
11
+
12
+ | merge_statement_v2
12
13
  | rawsql_statement
13
14
 
14
15
  _TERMINATOR: ";"i /\s*/
@@ -34,7 +35,7 @@
34
35
  prop_ident: "<" IDENTIFIER ("," IDENTIFIER )* ","? ">" "." IDENTIFIER
35
36
 
36
37
  // datasource concepts
37
- datasource: "datasource" IDENTIFIER "(" column_assignment_list ")" grain_clause? (address | query)
38
+ datasource: "datasource" IDENTIFIER "(" column_assignment_list ")" grain_clause? (address | query) where?
38
39
 
39
40
  grain_clause: "grain" "(" column_list ")"
40
41
 
@@ -69,8 +70,8 @@
69
70
 
70
71
  align_clause: align_item ("AND"i align_item)* "AND"i?
71
72
 
72
- // merge statemment
73
- merge_statement: "merge" IDENTIFIER ("," IDENTIFIER)* ","?
73
+ // merge_v2
74
+ merge_statement_v2: "merge"i IDENTIFIER "into"i SHORTHAND_MODIFIER? IDENTIFIER
74
75
 
75
76
  // raw sql statement
76
77
  rawsql_statement: "raw_sql"i "(" MULTILINE_STRING ")"
@@ -125,9 +126,13 @@
125
126
 
126
127
  !logical_operator: "and"i | "or"i
127
128
 
128
- conditional: expr logical_operator expr
129
+ condition_parenthetical: "(" conditional ")"
130
+
131
+ _condition_unit: (expr | condition_parenthetical)
132
+
133
+ conditional: _condition_unit (logical_operator _condition_unit)*
129
134
 
130
- where: "WHERE"i expr+
135
+ where: "WHERE"i conditional
131
136
 
132
137
  !array_comparison: ( ("NOT"i "IN"i) | "IN"i)
133
138
 
@@ -150,7 +155,7 @@
150
155
  index_access: expr "[" int_lit "]"
151
156
  attr_access: expr "[" _string_lit "]"
152
157
 
153
- expr: _constant_functions | window_item | filter_item | subselect_comparison | between_comparison | fgroup | aggregate_functions | unnest | _static_functions | literal | concept_lit | index_access | attr_access | parenthetical | expr_tuple | comparison | conditional | alt_like
158
+ expr: _constant_functions | window_item | filter_item | subselect_comparison | between_comparison | fgroup | aggregate_functions | unnest | _static_functions | literal | concept_lit | index_access | attr_access | parenthetical | expr_tuple | comparison | alt_like
154
159
 
155
160
  // functions
156
161
 
@@ -168,7 +173,7 @@
168
173
  fcast: "cast"i "(" expr "as"i data_type ")"
169
174
  concat: ("concat"i "(" (expr ",")* expr ")") | (expr "||" expr)
170
175
  fcoalesce: "coalesce"i "(" (expr ",")* expr ")"
171
- fcase_when: "WHEN"i (expr | conditional) "THEN"i expr
176
+ fcase_when: "WHEN"i conditional "THEN"i expr
172
177
  fcase_else: "ELSE"i expr
173
178
  fcase: "CASE"i (fcase_when)* (fcase_else)? "END"i
174
179
  len: "len"i "(" expr ")"
@@ -1,83 +0,0 @@
1
- trilogy/__init__.py,sha256=fYN8J2EkHXTX5cKVWpbBMQHilscbkhmZF-pMLoMqHFc,292
2
- trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- trilogy/constants.py,sha256=u2dNxhwy0v-6HrvG1GcpDVvuhzdTH5fuyYNCxDPlr2E,770
4
- trilogy/engine.py,sha256=R5ubIxYyrxRExz07aZCUfrTsoXCHQ8DKFTDsobXdWdA,1102
5
- trilogy/executor.py,sha256=5cRbU4Rj7p1pNV76rfp1pz704Hx_0q8_O8HFURjgXxQ,11016
6
- trilogy/parser.py,sha256=UtuqSiGiCjpMAYgo1bvNq-b7NSzCA5hzbUW31RXaMII,281
7
- trilogy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- trilogy/utility.py,sha256=zM__8r29EsyDW7K9VOHz8yvZC2bXFzh7xKy3cL7GKsk,707
9
- trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- trilogy/core/constants.py,sha256=LL8NLvxb3HRnAjvofyLRXqQJijLcYiXAQYQzGarVD-g,128
11
- trilogy/core/enums.py,sha256=9IzCv0Hzzr3OTJQWzg9yHV_b46Jvk47RNoZjdBi2Its,5678
12
- trilogy/core/env_processor.py,sha256=SU-jpaGfoWLe9sGTeQYG1qjVnwGQ7TwctmnJRlfzluc,1459
13
- trilogy/core/environment_helpers.py,sha256=mzBDHhdF9ssZ_-LY8CcaM_ddfJavkpRYrFImUd3cjXI,5972
14
- trilogy/core/ergonomics.py,sha256=w3gwXdgrxNHCuaRdyKg73t6F36tj-wIjQf47WZkHmJk,1465
15
- trilogy/core/exceptions.py,sha256=NvV_4qLOgKXbpotgRf7c8BANDEvHxlqRPaA53IThQ2o,561
16
- trilogy/core/functions.py,sha256=fWuuPOeWRAiukTkXvTEu_NCSbu6eUmogtLGpC4ey2FI,9465
17
- trilogy/core/graph_models.py,sha256=oJUMSpmYhqXlavckHLpR07GJxuQ8dZ1VbB1fB0KaS8c,2036
18
- trilogy/core/internal.py,sha256=jNGFHKENnbMiMCtAgsnLZYVSENDK4b5ALecXFZpTDzQ,1075
19
- trilogy/core/models.py,sha256=Mp6MB8Gl2CZcPDWkofPX8Mym1ZvT0OUS0dKAUWCj7K8,116419
20
- trilogy/core/optimization.py,sha256=oM3Ry7UpbpTSm2xNkmWx70OHd2V2vWRjM72sZpsZfb8,4116
21
- trilogy/core/query_processor.py,sha256=clIRJ6IcsqIVBPKFsxt8bqCLsLyajvAu02MUIcKQhTo,15713
22
- trilogy/core/optimizations/__init__.py,sha256=pxRzNzd2g8oRMy4f_ub5va6bNS2pd4hnyp9JBzTKc1E,300
23
- trilogy/core/optimizations/base_optimization.py,sha256=tWWT-xnTbnEU-mNi_isMNbywm8B9WTRsNFwGpeh3rqE,468
24
- trilogy/core/optimizations/inline_constant.py,sha256=neZOFjX7M2pzQ-8m-f8nApy_MfJuowX6SzcGwGFt5w4,927
25
- trilogy/core/optimizations/inline_datasource.py,sha256=BSp54fwF4RRwInd-09pggemC7JuXj-uqGzi32ufeqYo,2171
26
- trilogy/core/optimizations/predicate_pushdown.py,sha256=sIojWvoYp6k_ANCyVqxCpEyLY_GLmzsG-Sghj0cbk3k,4135
27
- trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- trilogy/core/processing/concept_strategies_v3.py,sha256=MYrpNMidqvPOg123RekOcqVTjcj03i_538gBo0MzoWE,23432
29
- trilogy/core/processing/graph_utils.py,sha256=ulCJ4hYAISbUxLD6VM2fah9RBPGIXSEHEPeRBSFl0Rs,1197
30
- trilogy/core/processing/utility.py,sha256=acxH5448-j8JXqxMRibyAxjz1Wqu7QudbR0PfMuucww,9902
31
- trilogy/core/processing/node_generators/__init__.py,sha256=LIs6uBEum8LDc-26zjyAwjxa-ay2ok9tKtPjDNvbVkE,757
32
- trilogy/core/processing/node_generators/basic_node.py,sha256=HJnIhZLgkUdorKYcofe-QnKSM3Lf_3QO91cbSJhsqf4,2242
33
- trilogy/core/processing/node_generators/common.py,sha256=liZDth7mvhkF_sUFXK7JitJsiaKD132w3ySLbF7l-nE,8956
34
- trilogy/core/processing/node_generators/concept_merge_node.py,sha256=x4M8VVZZmBcqHDY1uq7M9KGKCBwjU6mcE_x2BOEk2Mg,7328
35
- trilogy/core/processing/node_generators/filter_node.py,sha256=y_tqYe2So18vWHASMwVPLzDO-PnyQCO-MAlI4B-rY3Y,4526
36
- trilogy/core/processing/node_generators/group_node.py,sha256=xWI1xNIXEOj6jlRGD9hcv2_vVNvY6lpzJl6pQ8HuFBE,2988
37
- trilogy/core/processing/node_generators/group_to_node.py,sha256=BzPdYwzoo8gRMH7BDffTTXq4z-mjfCEzvfB5I-P0_nw,2941
38
- trilogy/core/processing/node_generators/multiselect_node.py,sha256=vP84dnLQy6dtypi6mUbt9sMAcmmrTgQ1Oz4GI6X1IEo,6421
39
- trilogy/core/processing/node_generators/node_merge_node.py,sha256=wNDHAbRrKSjsns-EROM_G12mRyOMjbcWpYav2uefXOE,6045
40
- trilogy/core/processing/node_generators/rowset_node.py,sha256=eNG6rfLifUKraoRGxE8pesQMy5cKT6R5XNIaa3Wuiwk,6081
41
- trilogy/core/processing/node_generators/select_node.py,sha256=Qb00Kizsv-877UMkGfusl5jXKXMZtZTtLks5pxU07SU,20698
42
- trilogy/core/processing/node_generators/unnest_node.py,sha256=6CH66eGwpadNX7TzUhWZ8aqIisOtQeHINbLV6X3QBUk,1779
43
- trilogy/core/processing/node_generators/window_node.py,sha256=9nXUXUgQrNczU1gaOqhOZPNzCUxw-lkxt0R7HORI6ss,2582
44
- trilogy/core/processing/nodes/__init__.py,sha256=baODkJfvUoWEEbu843GEd7snubwLeOG5FQ8l-CwIaC8,3928
45
- trilogy/core/processing/nodes/base_node.py,sha256=yhjmsAUmhHDqgbQjz_9YdfP-M5pj4xbrPRDF6Y4XVuw,10498
46
- trilogy/core/processing/nodes/filter_node.py,sha256=rDw4vfE6tqWxuKT0arihVmIOoOWDDCyzRA-2yONX_Ek,1860
47
- trilogy/core/processing/nodes/group_node.py,sha256=vzeU9J4xMhRrPj4-KPJTgNbH-KFu2ZS8b57SOynsdw0,4448
48
- trilogy/core/processing/nodes/merge_node.py,sha256=FvSiTWKOzaUsXBkf6wJD8QQqQxp_aphS_I5VzNRw8Yo,13600
49
- trilogy/core/processing/nodes/select_node_v2.py,sha256=ERCflBFzKpD5SzweMevnJLyQnxmF_-IQ6VRu5yVeiBg,6552
50
- trilogy/core/processing/nodes/unnest_node.py,sha256=JFtm90IVM-46aCYkTNIaJah6v9ApAfonjVhcVM1HmDE,1903
51
- trilogy/core/processing/nodes/window_node.py,sha256=X7qxLUKd3tekjUUsmH_4vz5b-U89gMnGd04VBxuu2Ns,1280
52
- trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
- trilogy/dialect/base.py,sha256=ycsxbUL68DsodOsHEgEoNLKMn5vgRN3sDIRfiH9fQDs,31719
54
- trilogy/dialect/bigquery.py,sha256=9vxQn2BMv_oTGQSWQpoN5ho_OgqMWaHH9e-5vQVf44c,2906
55
- trilogy/dialect/common.py,sha256=zWrYmvevlXznocw9uGHmY5Ws1rp_kICm9zA_ulTe4eg,2165
56
- trilogy/dialect/config.py,sha256=tLVEMctaTDhUgARKXUNfHUcIolGaALkQ0RavUvXAY4w,2994
57
- trilogy/dialect/duckdb.py,sha256=Ddyt68sr8IL2HnZMenyytoD65FXwY_O2pz1McyS0bis,3075
58
- trilogy/dialect/enums.py,sha256=4NdpsydBpDn6jnh0JzFz5VvQEtnShErWtWHVyT6TNpw,3948
59
- trilogy/dialect/postgres.py,sha256=r47xbCA7nfEYENofiVfLZ-SnReNfDmUmW4OSHVkkP4E,3206
60
- trilogy/dialect/presto.py,sha256=UxBodRiV3szpFcQlcjoJaGXEwAhZJf_OT7dHczYvO80,3092
61
- trilogy/dialect/snowflake.py,sha256=N3HknYgN-fjD7BLX1Ucj-ss_ku2Ox8DgLsF3BIHutHo,2941
62
- trilogy/dialect/sql_server.py,sha256=HX68vNTrcDaTnOxe6Zbx_PBgrO42e2VuThxO6CYQ2cY,3026
63
- trilogy/hooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
64
- trilogy/hooks/base_hook.py,sha256=Xkb-A2qCHozYjum0A36zOy5PwTVwrP3NLDF0U2GpgHo,1100
65
- trilogy/hooks/graph_hook.py,sha256=i-Tv9sxZU0sMc-God8bLLz-nAg4-wYafogZtHaU8LXw,801
66
- trilogy/hooks/query_debugger.py,sha256=D2VJUcyvQrVJ8sT6FCMKR3NKTfVrgZQ7gly91avHHpw,4222
67
- trilogy/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
- trilogy/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
69
- trilogy/parsing/common.py,sha256=iR3fiiZ7w8VJuUGrQ0v06XGDXov81f4z1ZlFnj6y40E,5804
70
- trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
71
- trilogy/parsing/exceptions.py,sha256=92E5i2frv5hj9wxObJZsZqj5T6bglvPzvdvco_vW1Zk,38
72
- trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
73
- trilogy/parsing/parse_engine.py,sha256=F1ok96qT6EhKRKV1Q_YzfHxMFtNV8qAXopK8NaePgU4,57080
74
- trilogy/parsing/render.py,sha256=TnLf5fg4wimpd9EvhLU-FMDwpyW9pesoedBZ0RrmWD4,11810
75
- trilogy/parsing/trilogy.lark,sha256=1AZbQGpNmpm4KamAXA5IWcuOr2B8Gb8kUJcAOmKf_zY,10862
76
- trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
- trilogy/scripts/trilogy.py,sha256=PHxvv6f2ODv0esyyhWxlARgra8dVhqQhYl0lTrSyVNo,3729
78
- pytrilogy-0.0.1.118.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
79
- pytrilogy-0.0.1.118.dist-info/METADATA,sha256=tZ1kQcOTJ5JTYb5tqlfdo7g-o94TC0tKmcdqLtJ35bI,7878
80
- pytrilogy-0.0.1.118.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
81
- pytrilogy-0.0.1.118.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
82
- pytrilogy-0.0.1.118.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
83
- pytrilogy-0.0.1.118.dist-info/RECORD,,
@@ -1,214 +0,0 @@
1
- from trilogy.core.models import (
2
- Concept,
3
- Environment,
4
- MergeStatement,
5
- )
6
- from trilogy.core.processing.nodes import MergeNode, NodeJoin, History
7
- from trilogy.core.processing.nodes.base_node import concept_list_to_grain, StrategyNode
8
- from typing import List
9
-
10
- from trilogy.core.enums import JoinType, PurposeLineage
11
- from trilogy.constants import logger
12
- from trilogy.core.processing.utility import padding
13
- from trilogy.core.processing.node_generators.common import concept_to_relevant_joins
14
- from itertools import combinations
15
- from trilogy.core.processing.node_generators.common import resolve_join_order
16
- from trilogy.utility import unique
17
-
18
-
19
- LOGGER_PREFIX = "[GEN_CONCEPT_MERGE_NODE]"
20
-
21
-
22
- def merge_joins(
23
- parents: List[StrategyNode], merge_concepts: List[Concept]
24
- ) -> List[NodeJoin]:
25
- output = []
26
- for left, right in combinations(parents, 2):
27
- output.append(
28
- NodeJoin(
29
- left_node=left,
30
- right_node=right,
31
- concepts=merge_concepts,
32
- join_type=JoinType.FULL,
33
- filter_to_mutual=True,
34
- )
35
- )
36
- return resolve_join_order(output)
37
-
38
-
39
- def gen_concept_merge_node(
40
- concept: Concept,
41
- local_optional: List[Concept],
42
- environment: Environment,
43
- g,
44
- depth: int,
45
- source_concepts,
46
- history: History | None = None,
47
- ) -> MergeNode | None:
48
- if not isinstance(concept.lineage, MergeStatement):
49
- logger.info(
50
- f"{padding(depth)}{LOGGER_PREFIX} Cannot generate merge node for {concept}"
51
- )
52
- return None
53
- lineage: MergeStatement = concept.lineage
54
-
55
- base_parents: List[StrategyNode] = []
56
-
57
- # get additional concepts that should be merged across the environments
58
- additional_merge: List[Concept] = [*lineage.concepts]
59
- target_namespaces = set(x.namespace for x in [concept] + local_optional)
60
- for x in local_optional:
61
- if x.address in environment.merged_concepts:
62
- ms = environment.merged_concepts[x.address].lineage
63
- assert isinstance(ms, MergeStatement)
64
- additional_merge += ms.concepts
65
-
66
- for select in lineage.concepts:
67
- # if it's a merge concept, filter it out of the optional
68
- if select.namespace not in target_namespaces:
69
- continue
70
- sub_optional = [
71
- x
72
- for x in local_optional
73
- if x.address not in environment.merged_concepts
74
- and x.namespace == select.namespace
75
- ]
76
-
77
- sub_additional_merge = [
78
- x for x in additional_merge if x.namespace == select.namespace
79
- ]
80
- sub_optional += sub_additional_merge
81
- final: List[Concept] = unique([select] + sub_optional, "address")
82
- logger.info(
83
- f"{padding(depth)}{LOGGER_PREFIX} generating concept merge parent node with {[x.address for x in final]}"
84
- )
85
- snode: StrategyNode = source_concepts(
86
- mandatory_list=final,
87
- environment=environment,
88
- g=g,
89
- depth=depth + 1,
90
- history=history,
91
- )
92
-
93
- if not snode:
94
- logger.info(
95
- f"{padding(depth)}{LOGGER_PREFIX} Cannot generate merge node for {concept}"
96
- )
97
- return None
98
- for x in sub_additional_merge:
99
- snode.add_output_concept(environment.merged_concepts[x.address])
100
- base_parents.append(snode)
101
-
102
- node_joins = merge_joins(
103
- base_parents,
104
- unique(
105
- [environment.merged_concepts[x.address] for x in additional_merge],
106
- "address",
107
- ),
108
- )
109
-
110
- enrichment = set([x.address for x in local_optional])
111
- outputs = [
112
- x
113
- for y in base_parents
114
- for x in y.output_concepts
115
- if x.derivation != PurposeLineage.MERGE
116
- ]
117
-
118
- additional_relevant = [x for x in outputs if x.address in enrichment]
119
- final_outputs = outputs + additional_relevant + [concept]
120
- virtual_outputs = [x for x in final_outputs if x.derivation == PurposeLineage.MERGE]
121
- node = MergeNode(
122
- input_concepts=[x for y in base_parents for x in y.output_concepts],
123
- output_concepts=[
124
- x for x in final_outputs if x.derivation != PurposeLineage.MERGE
125
- ],
126
- environment=environment,
127
- g=g,
128
- depth=depth,
129
- parents=base_parents,
130
- node_joins=node_joins,
131
- virtual_output_concepts=virtual_outputs,
132
- )
133
-
134
- qds = node.rebuild_cache()
135
-
136
- # assume grain to be outoput of select
137
- # but don't include anything aggregate at this point
138
- qds.grain = concept_list_to_grain(
139
- node.output_concepts, parent_sources=qds.datasources
140
- )
141
- possible_joins = concept_to_relevant_joins(additional_relevant)
142
- if not local_optional:
143
- logger.info(
144
- f"{padding(depth)}{LOGGER_PREFIX} no enriched required for merge concept node; exiting early"
145
- )
146
- return node
147
- if not possible_joins:
148
- logger.info(
149
- f"{padding(depth)}{LOGGER_PREFIX} no possible joins for merge concept node; exiting early"
150
- )
151
- return node
152
- if all(
153
- [x.address in [y.address for y in node.output_concepts] for x in local_optional]
154
- ):
155
- logger.info(
156
- f"{padding(depth)}{LOGGER_PREFIX} all enriched concepts returned from base merge concept node; exiting early"
157
- )
158
- return node
159
- missing = [
160
- x
161
- for x in local_optional
162
- if x.address not in [y.address for y in node.output_concepts]
163
- ]
164
- logger.info(
165
- f"{padding(depth)}{LOGGER_PREFIX} generating merge concept enrichment node for missing {[x.address for x in missing]}"
166
- )
167
- enrich_node: MergeNode = source_concepts( # this fetches the parent + join keys
168
- # to then connect to the rest of the query
169
- mandatory_list=additional_relevant + missing,
170
- environment=environment,
171
- g=g,
172
- depth=depth + 1,
173
- history=history,
174
- )
175
- if not enrich_node:
176
- logger.info(
177
- f"{padding(depth)}{LOGGER_PREFIX} Cannot generate merge concept enrichment node for {concept.address} with optional {[x.address for x in local_optional]}, returning just merge concept"
178
- )
179
- return node
180
-
181
- # we still need the hidden concepts to be returned to the search
182
- # since they must be on the final node
183
- # to avoid further recursion
184
- # TODO: let the downstream search know they were found
185
- return MergeNode(
186
- input_concepts=enrich_node.output_concepts + node.output_concepts,
187
- # also filter out the
188
- output_concepts=[
189
- x
190
- for x in node.output_concepts + local_optional
191
- if x.derivation != PurposeLineage.MERGE
192
- ],
193
- hidden_concepts=[],
194
- environment=environment,
195
- g=g,
196
- depth=depth,
197
- parents=[
198
- # this node gets the window
199
- node,
200
- # this node gets enrichment
201
- enrich_node,
202
- ],
203
- node_joins=[
204
- NodeJoin(
205
- left_node=enrich_node,
206
- right_node=node,
207
- concepts=possible_joins,
208
- filter_to_mutual=False,
209
- join_type=JoinType.LEFT_OUTER,
210
- )
211
- ],
212
- partial_concepts=node.partial_concepts,
213
- virtual_output_concepts=virtual_outputs,
214
- )