pytrilogy 0.0.2.57__py3-none-any.whl → 0.0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. {pytrilogy-0.0.2.57.dist-info → pytrilogy-0.0.3.0.dist-info}/METADATA +9 -2
  2. pytrilogy-0.0.3.0.dist-info/RECORD +99 -0
  3. {pytrilogy-0.0.2.57.dist-info → pytrilogy-0.0.3.0.dist-info}/WHEEL +1 -1
  4. trilogy/__init__.py +2 -2
  5. trilogy/core/enums.py +1 -7
  6. trilogy/core/env_processor.py +17 -5
  7. trilogy/core/environment_helpers.py +11 -25
  8. trilogy/core/exceptions.py +4 -0
  9. trilogy/core/functions.py +695 -261
  10. trilogy/core/graph_models.py +10 -10
  11. trilogy/core/internal.py +11 -2
  12. trilogy/core/models/__init__.py +0 -0
  13. trilogy/core/models/author.py +2110 -0
  14. trilogy/core/models/build.py +1845 -0
  15. trilogy/core/models/build_environment.py +151 -0
  16. trilogy/core/models/core.py +370 -0
  17. trilogy/core/models/datasource.py +297 -0
  18. trilogy/core/models/environment.py +696 -0
  19. trilogy/core/models/execute.py +931 -0
  20. trilogy/core/optimization.py +17 -22
  21. trilogy/core/optimizations/base_optimization.py +1 -1
  22. trilogy/core/optimizations/inline_constant.py +6 -6
  23. trilogy/core/optimizations/inline_datasource.py +17 -11
  24. trilogy/core/optimizations/predicate_pushdown.py +17 -16
  25. trilogy/core/processing/concept_strategies_v3.py +181 -146
  26. trilogy/core/processing/graph_utils.py +1 -1
  27. trilogy/core/processing/node_generators/basic_node.py +19 -18
  28. trilogy/core/processing/node_generators/common.py +51 -45
  29. trilogy/core/processing/node_generators/filter_node.py +26 -13
  30. trilogy/core/processing/node_generators/group_node.py +26 -21
  31. trilogy/core/processing/node_generators/group_to_node.py +13 -10
  32. trilogy/core/processing/node_generators/multiselect_node.py +60 -43
  33. trilogy/core/processing/node_generators/node_merge_node.py +76 -38
  34. trilogy/core/processing/node_generators/rowset_node.py +59 -36
  35. trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +27 -34
  36. trilogy/core/processing/node_generators/select_merge_node.py +161 -64
  37. trilogy/core/processing/node_generators/select_node.py +13 -13
  38. trilogy/core/processing/node_generators/union_node.py +12 -11
  39. trilogy/core/processing/node_generators/unnest_node.py +9 -7
  40. trilogy/core/processing/node_generators/window_node.py +19 -16
  41. trilogy/core/processing/nodes/__init__.py +21 -18
  42. trilogy/core/processing/nodes/base_node.py +92 -77
  43. trilogy/core/processing/nodes/filter_node.py +19 -13
  44. trilogy/core/processing/nodes/group_node.py +55 -40
  45. trilogy/core/processing/nodes/merge_node.py +47 -38
  46. trilogy/core/processing/nodes/select_node_v2.py +54 -40
  47. trilogy/core/processing/nodes/union_node.py +5 -7
  48. trilogy/core/processing/nodes/unnest_node.py +7 -11
  49. trilogy/core/processing/nodes/window_node.py +9 -4
  50. trilogy/core/processing/utility.py +108 -80
  51. trilogy/core/query_processor.py +67 -49
  52. trilogy/core/statements/__init__.py +0 -0
  53. trilogy/core/statements/author.py +413 -0
  54. trilogy/core/statements/build.py +0 -0
  55. trilogy/core/statements/common.py +30 -0
  56. trilogy/core/statements/execute.py +42 -0
  57. trilogy/dialect/base.py +152 -111
  58. trilogy/dialect/common.py +9 -10
  59. trilogy/dialect/duckdb.py +1 -1
  60. trilogy/dialect/enums.py +4 -2
  61. trilogy/dialect/presto.py +1 -1
  62. trilogy/dialect/sql_server.py +1 -1
  63. trilogy/executor.py +44 -32
  64. trilogy/hooks/base_hook.py +6 -4
  65. trilogy/hooks/query_debugger.py +110 -93
  66. trilogy/parser.py +1 -1
  67. trilogy/parsing/common.py +303 -64
  68. trilogy/parsing/parse_engine.py +263 -617
  69. trilogy/parsing/render.py +50 -26
  70. trilogy/scripts/trilogy.py +2 -1
  71. pytrilogy-0.0.2.57.dist-info/RECORD +0 -87
  72. trilogy/core/models.py +0 -4960
  73. {pytrilogy-0.0.2.57.dist-info → pytrilogy-0.0.3.0.dist-info}/LICENSE.md +0 -0
  74. {pytrilogy-0.0.2.57.dist-info → pytrilogy-0.0.3.0.dist-info}/entry_points.txt +0 -0
  75. {pytrilogy-0.0.2.57.dist-info → pytrilogy-0.0.3.0.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,8 @@
1
1
  from typing import List
2
2
 
3
3
  from trilogy.constants import logger
4
- from trilogy.core.models import Concept, Function, WhereClause
4
+ from trilogy.core.models.build import BuildConcept, BuildFunction, BuildWhereClause
5
+ from trilogy.core.models.build_environment import BuildEnvironment
5
6
  from trilogy.core.processing.nodes import History, StrategyNode, UnnestNode
6
7
  from trilogy.core.processing.utility import padding
7
8
 
@@ -9,17 +10,17 @@ LOGGER_PREFIX = "[GEN_UNNEST_NODE]"
9
10
 
10
11
 
11
12
  def gen_unnest_node(
12
- concept: Concept,
13
- local_optional: List[Concept],
14
- environment,
13
+ concept: BuildConcept,
14
+ local_optional: List[BuildConcept],
15
+ history: History,
16
+ environment: BuildEnvironment,
15
17
  g,
16
18
  depth: int,
17
19
  source_concepts,
18
- history: History | None = None,
19
- conditions: WhereClause | None = None,
20
+ conditions: BuildWhereClause | None = None,
20
21
  ) -> StrategyNode | None:
21
22
  arguments = []
22
- if isinstance(concept.lineage, Function):
23
+ if isinstance(concept.lineage, BuildFunction):
23
24
  arguments = concept.lineage.concept_arguments
24
25
 
25
26
  equivalent_optional = [x for x in local_optional if x.lineage == concept.lineage]
@@ -56,6 +57,7 @@ def gen_unnest_node(
56
57
  output_concepts=base.output_concepts,
57
58
  environment=environment,
58
59
  parents=[base],
60
+ # conditions=conditions.conditional if conditions else None,
59
61
  preexisting_conditions=conditions.conditional if conditions else None,
60
62
  )
61
63
  qds = new.resolve()
@@ -1,7 +1,8 @@
1
1
  from typing import List
2
2
 
3
3
  from trilogy.constants import logger
4
- from trilogy.core.models import Concept, Environment, WhereClause, WindowItem
4
+ from trilogy.core.models.build import BuildConcept, BuildWhereClause, BuildWindowItem
5
+ from trilogy.core.models.build_environment import BuildEnvironment
5
6
  from trilogy.core.processing.nodes import History, StrategyNode, WindowNode
6
7
  from trilogy.core.processing.utility import padding
7
8
  from trilogy.utility import unique
@@ -9,40 +10,42 @@ from trilogy.utility import unique
9
10
  LOGGER_PREFIX = "[GEN_WINDOW_NODE]"
10
11
 
11
12
 
13
+ WINDOW_TYPES = (BuildWindowItem,)
14
+
15
+
12
16
  def resolve_window_parent_concepts(
13
- concept: Concept, environment: Environment
14
- ) -> tuple[Concept, List[Concept]]:
15
- if not isinstance(concept.lineage, WindowItem):
17
+ concept: BuildConcept, environment: BuildEnvironment
18
+ ) -> tuple[BuildConcept, List[BuildConcept]]:
19
+ if not isinstance(concept.lineage, WINDOW_TYPES):
16
20
  raise ValueError
17
21
  base = []
22
+ logger.info(concept.lineage)
18
23
  if concept.lineage.over:
19
24
  base += concept.lineage.over
20
25
  if concept.lineage.order_by:
21
26
  for item in concept.lineage.order_by:
22
- # TODO: we do want to use the rehydrated value, but
23
- # that introduces a circular dependency on an aggregate
24
- # that is grouped by a window
25
- # need to figure out how to resolve this
26
- # base += [environment.concepts[item.expr.output.address]]
27
- base += [item.expr.output]
27
+ base += item.concept_arguments
28
28
  return concept.lineage.content, unique(base, "address")
29
29
 
30
30
 
31
31
  def gen_window_node(
32
- concept: Concept,
33
- local_optional: list[Concept],
34
- environment: Environment,
32
+ concept: BuildConcept,
33
+ local_optional: list[BuildConcept],
34
+ environment: BuildEnvironment,
35
35
  g,
36
36
  depth: int,
37
37
  source_concepts,
38
38
  history: History | None = None,
39
- conditions: WhereClause | None = None,
39
+ conditions: BuildWhereClause | None = None,
40
40
  ) -> StrategyNode | None:
41
41
  base, parent_concepts = resolve_window_parent_concepts(concept, environment)
42
+ logger.info(
43
+ f"{padding(depth)}{LOGGER_PREFIX} generating window node for {concept} with parents {parent_concepts}"
44
+ )
42
45
  equivalent_optional = [
43
46
  x
44
47
  for x in local_optional
45
- if isinstance(x.lineage, WindowItem)
48
+ if isinstance(x.lineage, WINDOW_TYPES)
46
49
  and resolve_window_parent_concepts(x, environment)[1] == parent_concepts
47
50
  ]
48
51
 
@@ -52,7 +55,7 @@ def gen_window_node(
52
55
  targets = [base]
53
56
  if equivalent_optional:
54
57
  for x in equivalent_optional:
55
- assert isinstance(x.lineage, WindowItem)
58
+ assert isinstance(x.lineage, WINDOW_TYPES)
56
59
  targets.append(x.lineage.content)
57
60
 
58
61
  parent_node: StrategyNode = source_concepts(
@@ -1,6 +1,8 @@
1
1
  from pydantic import BaseModel, ConfigDict, Field
2
2
 
3
- from trilogy.core.models import Concept, Environment, WhereClause
3
+ from trilogy.core.models.build import BuildConcept, BuildWhereClause
4
+ from trilogy.core.models.build_environment import BuildEnvironment
5
+ from trilogy.core.models.environment import Environment
4
6
 
5
7
  from .base_node import NodeJoin, StrategyNode
6
8
  from .filter_node import FilterNode
@@ -13,6 +15,7 @@ from .window_node import WindowNode
13
15
 
14
16
 
15
17
  class History(BaseModel):
18
+ base_environment: Environment
16
19
  history: dict[str, StrategyNode | None] = Field(default_factory=dict)
17
20
  select_history: dict[str, StrategyNode | None] = Field(default_factory=dict)
18
21
  started: set[str] = Field(default_factory=set)
@@ -20,9 +23,9 @@ class History(BaseModel):
20
23
 
21
24
  def _concepts_to_lookup(
22
25
  self,
23
- search: list[Concept],
26
+ search: list[BuildConcept],
24
27
  accept_partial: bool,
25
- conditions: WhereClause | None = None,
28
+ conditions: BuildWhereClause | None = None,
26
29
  ) -> str:
27
30
  if conditions:
28
31
  return (
@@ -34,10 +37,10 @@ class History(BaseModel):
34
37
 
35
38
  def search_to_history(
36
39
  self,
37
- search: list[Concept],
40
+ search: list[BuildConcept],
38
41
  accept_partial: bool,
39
42
  output: StrategyNode | None,
40
- conditions: WhereClause | None = None,
43
+ conditions: BuildWhereClause | None = None,
41
44
  ):
42
45
  self.history[
43
46
  self._concepts_to_lookup(search, accept_partial, conditions=conditions)
@@ -45,8 +48,8 @@ class History(BaseModel):
45
48
 
46
49
  def get_history(
47
50
  self,
48
- search: list[Concept],
49
- conditions: WhereClause | None = None,
51
+ search: list[BuildConcept],
52
+ conditions: BuildWhereClause | None = None,
50
53
  accept_partial: bool = False,
51
54
  parent_key: str = "",
52
55
  ) -> StrategyNode | None | bool:
@@ -68,9 +71,9 @@ class History(BaseModel):
68
71
 
69
72
  def log_start(
70
73
  self,
71
- search: list[Concept],
74
+ search: list[BuildConcept],
72
75
  accept_partial: bool = False,
73
- conditions: WhereClause | None = None,
76
+ conditions: BuildWhereClause | None = None,
74
77
  ):
75
78
  self.started.add(
76
79
  self._concepts_to_lookup(
@@ -82,9 +85,9 @@ class History(BaseModel):
82
85
 
83
86
  def check_started(
84
87
  self,
85
- search: list[Concept],
88
+ search: list[BuildConcept],
86
89
  accept_partial: bool = False,
87
- conditions: WhereClause | None = None,
90
+ conditions: BuildWhereClause | None = None,
88
91
  ):
89
92
  return (
90
93
  self._concepts_to_lookup(
@@ -97,12 +100,12 @@ class History(BaseModel):
97
100
 
98
101
  def _select_concepts_to_lookup(
99
102
  self,
100
- main: Concept,
101
- search: list[Concept],
103
+ main: BuildConcept,
104
+ search: list[BuildConcept],
102
105
  accept_partial: bool,
103
106
  fail_if_not_found: bool,
104
107
  accept_partial_optional: bool,
105
- conditions: WhereClause | None = None,
108
+ conditions: BuildWhereClause | None = None,
106
109
  ) -> str:
107
110
  return (
108
111
  str(main.address)
@@ -116,16 +119,16 @@ class History(BaseModel):
116
119
 
117
120
  def gen_select_node(
118
121
  self,
119
- concept: Concept,
120
- local_optional: list[Concept],
121
- environment: Environment,
122
+ concept: BuildConcept,
123
+ local_optional: list[BuildConcept],
124
+ environment: BuildEnvironment,
122
125
  g,
123
126
  depth: int,
124
127
  source_concepts,
125
128
  fail_if_not_found: bool = False,
126
129
  accept_partial: bool = False,
127
130
  accept_partial_optional: bool = False,
128
- conditions: WhereClause | None = None,
131
+ conditions: BuildWhereClause | None = None,
129
132
  ) -> StrategyNode | None:
130
133
  from trilogy.core.processing.node_generators.select_node import gen_select_node
131
134
 
@@ -4,45 +4,45 @@ from typing import List, Optional
4
4
 
5
5
  from trilogy.core.enums import (
6
6
  BooleanOperator,
7
+ Derivation,
7
8
  JoinType,
8
- PurposeLineage,
9
- )
10
- from trilogy.core.models import (
11
- Comparison,
12
- Concept,
13
- ConceptPair,
14
- Conditional,
15
- Datasource,
16
- Environment,
17
- Grain,
18
- LooseConceptList,
19
- Parenthetical,
20
- QueryDatasource,
21
9
  SourceType,
22
- UnnestJoin,
23
10
  )
11
+ from trilogy.core.models.build import (
12
+ BuildComparison,
13
+ BuildConcept,
14
+ BuildConditional,
15
+ BuildDatasource,
16
+ BuildGrain,
17
+ BuildOrderBy,
18
+ BuildParenthetical,
19
+ LooseBuildConceptList,
20
+ )
21
+ from trilogy.core.models.build_environment import BuildEnvironment
22
+ from trilogy.core.models.execute import ConceptPair, QueryDatasource, UnnestJoin
24
23
  from trilogy.utility import unique
25
24
 
26
25
 
27
26
  def resolve_concept_map(
28
- inputs: List[QueryDatasource | Datasource],
29
- targets: List[Concept],
30
- inherited_inputs: List[Concept],
31
- full_joins: List[Concept] | None = None,
32
- ) -> dict[str, set[Datasource | QueryDatasource | UnnestJoin]]:
27
+ inputs: List[QueryDatasource | BuildDatasource],
28
+ targets: List[BuildConcept],
29
+ inherited_inputs: List[BuildConcept],
30
+ full_joins: List[BuildConcept] | None = None,
31
+ ) -> dict[str, set[BuildDatasource | QueryDatasource | UnnestJoin]]:
33
32
  targets = targets or []
34
- concept_map: dict[str, set[Datasource | QueryDatasource | UnnestJoin]] = (
33
+ concept_map: dict[str, set[BuildDatasource | QueryDatasource | UnnestJoin]] = (
35
34
  defaultdict(set)
36
35
  )
37
36
  full_addresses = {c.address for c in full_joins} if full_joins else set()
38
37
  inherited = set([t.address for t in inherited_inputs])
39
38
  for input in inputs:
40
39
  for concept in input.output_concepts:
41
- if concept.address not in input.non_partial_concept_addresses:
40
+ if concept.address not in input.full_concepts:
42
41
  continue
43
- if isinstance(input, QueryDatasource) and concept.address in [
44
- x.address for x in input.hidden_concepts
45
- ]:
42
+ if (
43
+ isinstance(input, QueryDatasource)
44
+ and concept.address in input.hidden_concepts
45
+ ):
46
46
  continue
47
47
  if concept.address in full_addresses:
48
48
  concept_map[concept.address].add(input)
@@ -75,8 +75,8 @@ def resolve_concept_map(
75
75
 
76
76
 
77
77
  def get_all_parent_partial(
78
- all_concepts: List[Concept], parents: List["StrategyNode"]
79
- ) -> List[Concept]:
78
+ all_concepts: List[BuildConcept], parents: List["StrategyNode"]
79
+ ) -> List[BuildConcept]:
80
80
  return unique(
81
81
  [
82
82
  c
@@ -102,8 +102,8 @@ def get_all_parent_partial(
102
102
 
103
103
 
104
104
  def get_all_parent_nullable(
105
- all_concepts: List[Concept], parents: List["StrategyNode"]
106
- ) -> List[Concept]:
105
+ all_concepts: List[BuildConcept], parents: List["StrategyNode"]
106
+ ) -> List[BuildConcept]:
107
107
  return unique(
108
108
  [
109
109
  c
@@ -126,28 +126,33 @@ class StrategyNode:
126
126
 
127
127
  def __init__(
128
128
  self,
129
- input_concepts: List[Concept],
130
- output_concepts: List[Concept],
131
- environment: Environment,
129
+ input_concepts: List[BuildConcept],
130
+ output_concepts: List[BuildConcept],
131
+ environment: BuildEnvironment,
132
132
  whole_grain: bool = False,
133
133
  parents: List["StrategyNode"] | None = None,
134
- partial_concepts: List[Concept] | None = None,
135
- nullable_concepts: List[Concept] | None = None,
134
+ partial_concepts: List[BuildConcept] | None = None,
135
+ nullable_concepts: List[BuildConcept] | None = None,
136
136
  depth: int = 0,
137
- conditions: Conditional | Comparison | Parenthetical | None = None,
138
- preexisting_conditions: Conditional | Comparison | Parenthetical | None = None,
137
+ conditions: (
138
+ BuildConditional | BuildComparison | BuildParenthetical | None
139
+ ) = None,
140
+ preexisting_conditions: (
141
+ BuildConditional | BuildComparison | BuildParenthetical | None
142
+ ) = None,
139
143
  force_group: bool | None = None,
140
- grain: Optional[Grain] = None,
141
- hidden_concepts: List[Concept] | None = None,
142
- existence_concepts: List[Concept] | None = None,
143
- virtual_output_concepts: List[Concept] | None = None,
144
+ grain: Optional[BuildGrain] = None,
145
+ hidden_concepts: set[str] | None = None,
146
+ existence_concepts: List[BuildConcept] | None = None,
147
+ virtual_output_concepts: List[BuildConcept] | None = None,
148
+ ordering: BuildOrderBy | None = None,
144
149
  ):
145
- self.input_concepts: List[Concept] = (
150
+ self.input_concepts: List[BuildConcept] = (
146
151
  unique(input_concepts, "address") if input_concepts else []
147
152
  )
148
- self.input_lcl = LooseConceptList(concepts=self.input_concepts)
149
- self.output_concepts: List[Concept] = unique(output_concepts, "address")
150
- self.output_lcl = LooseConceptList(concepts=self.output_concepts)
153
+ self.input_lcl = LooseBuildConceptList(concepts=self.input_concepts)
154
+ self.output_concepts: List[BuildConcept] = unique(output_concepts, "address")
155
+ self.output_lcl = LooseBuildConceptList(concepts=self.output_concepts)
151
156
 
152
157
  self.environment = environment
153
158
  self.whole_grain = whole_grain
@@ -159,13 +164,13 @@ class StrategyNode:
159
164
  self.nullable_concepts = nullable_concepts or get_all_parent_nullable(
160
165
  self.output_concepts, self.parents
161
166
  )
162
-
167
+ self.ordering = ordering
163
168
  self.depth = depth
164
169
  self.conditions = conditions
165
170
  self.grain = grain
166
171
  self.force_group = force_group
167
172
  self.tainted = False
168
- self.hidden_concepts = hidden_concepts or []
173
+ self.hidden_concepts = hidden_concepts or set()
169
174
  self.existence_concepts = existence_concepts or []
170
175
  self.virtual_output_concepts = virtual_output_concepts or []
171
176
  self.preexisting_conditions = preexisting_conditions
@@ -176,7 +181,7 @@ class StrategyNode:
176
181
  and self.preexisting_conditions
177
182
  and self.conditions != self.preexisting_conditions
178
183
  ):
179
- self.preexisting_conditions = Conditional(
184
+ self.preexisting_conditions = BuildConditional(
180
185
  left=self.conditions,
181
186
  right=self.preexisting_conditions,
182
187
  operator=BooleanOperator.AND,
@@ -206,16 +211,18 @@ class StrategyNode:
206
211
  return self
207
212
 
208
213
  def set_preexisting_conditions(
209
- self, conditions: Conditional | Comparison | Parenthetical
214
+ self, conditions: BuildConditional | BuildComparison | BuildParenthetical
210
215
  ):
211
216
  self.preexisting_conditions = conditions
212
217
  return self
213
218
 
214
- def add_condition(self, condition: Conditional | Comparison | Parenthetical):
219
+ def add_condition(
220
+ self, condition: BuildConditional | BuildComparison | BuildParenthetical
221
+ ):
215
222
  if self.conditions and condition == self.conditions:
216
223
  return self
217
224
  if self.conditions:
218
- self.conditions = Conditional(
225
+ self.conditions = BuildConditional(
219
226
  left=self.conditions, right=condition, operator=BooleanOperator.AND
220
227
  )
221
228
  else:
@@ -237,27 +244,29 @@ class StrategyNode:
237
244
  self.output_concepts, self.parents
238
245
  )
239
246
 
240
- self.partial_lcl = LooseConceptList(concepts=self.partial_concepts)
247
+ self.partial_lcl = LooseBuildConceptList(concepts=self.partial_concepts)
241
248
 
242
- def add_output_concepts(self, concepts: List[Concept], rebuild: bool = True):
249
+ def add_output_concepts(self, concepts: List[BuildConcept], rebuild: bool = True):
243
250
  for concept in concepts:
244
251
  if concept.address not in self.output_lcl.addresses:
245
252
  self.output_concepts.append(concept)
246
- self.output_lcl = LooseConceptList(concepts=self.output_concepts)
253
+ self.output_lcl = LooseBuildConceptList(concepts=self.output_concepts)
247
254
  if rebuild:
248
255
  self.rebuild_cache()
249
256
  return self
250
257
 
251
- def add_partial_concepts(self, concepts: List[Concept], rebuild: bool = True):
258
+ def add_partial_concepts(self, concepts: List[BuildConcept], rebuild: bool = True):
252
259
  for concept in concepts:
253
260
  if concept.address not in self.partial_lcl.addresses:
254
261
  self.partial_concepts.append(concept)
255
- self.partial_lcl = LooseConceptList(concepts=self.partial_concepts)
262
+ self.partial_lcl = LooseBuildConceptList(concepts=self.partial_concepts)
256
263
  if rebuild:
257
264
  self.rebuild_cache()
258
265
  return self
259
266
 
260
- def add_existence_concepts(self, concepts: List[Concept], rebuild: bool = True):
267
+ def add_existence_concepts(
268
+ self, concepts: List[BuildConcept], rebuild: bool = True
269
+ ):
261
270
  for concept in concepts:
262
271
  if concept.address not in self.output_concepts:
263
272
  self.existence_concepts.append(concept)
@@ -265,38 +274,40 @@ class StrategyNode:
265
274
  self.rebuild_cache()
266
275
  return self
267
276
 
268
- def set_output_concepts(self, concepts: List[Concept], rebuild: bool = True):
277
+ def set_output_concepts(self, concepts: List[BuildConcept], rebuild: bool = True):
269
278
  # exit if no changes
270
279
  if self.output_concepts == concepts:
271
280
  return self
272
281
  self.output_concepts = concepts
273
- self.output_lcl = LooseConceptList(concepts=self.output_concepts)
282
+ self.output_lcl = LooseBuildConceptList(concepts=self.output_concepts)
274
283
 
275
284
  if rebuild:
276
285
  self.rebuild_cache()
277
286
  return self
278
287
 
279
- def add_output_concept(self, concept: Concept, rebuild: bool = True):
288
+ def add_output_concept(self, concept: BuildConcept, rebuild: bool = True):
280
289
  return self.add_output_concepts([concept], rebuild)
281
290
 
282
- def hide_output_concepts(self, concepts: List[Concept], rebuild: bool = True):
291
+ def hide_output_concepts(self, concepts: List[BuildConcept], rebuild: bool = True):
283
292
  for x in concepts:
284
- self.hidden_concepts.append(x)
293
+ self.hidden_concepts.add(x.address)
285
294
  if rebuild:
286
295
  self.rebuild_cache()
287
296
  return self
288
297
 
289
- def unhide_output_concepts(self, concepts: List[Concept], rebuild: bool = True):
290
- self.hidden_concepts = [
291
- x for x in self.hidden_concepts if x.address not in concepts
292
- ]
298
+ def unhide_output_concepts(
299
+ self, concepts: List[BuildConcept], rebuild: bool = True
300
+ ):
301
+ self.hidden_concepts = set(x for x in self.hidden_concepts if x not in concepts)
293
302
  if rebuild:
294
303
  self.rebuild_cache()
295
304
  return self
296
305
 
297
- def remove_output_concepts(self, concepts: List[Concept], rebuild: bool = True):
306
+ def remove_output_concepts(
307
+ self, concepts: List[BuildConcept], rebuild: bool = True
308
+ ):
298
309
  for x in concepts:
299
- self.hidden_concepts.append(x)
310
+ self.hidden_concepts.add(x.address)
300
311
  addresses = [x.address for x in concepts]
301
312
  self.output_concepts = [
302
313
  x for x in self.output_concepts if x.address not in addresses
@@ -306,7 +317,7 @@ class StrategyNode:
306
317
  return self
307
318
 
308
319
  @property
309
- def usable_outputs(self) -> list[Concept]:
320
+ def usable_outputs(self) -> list[BuildConcept]:
310
321
  return [
311
322
  x for x in self.output_concepts if x.address not in self.hidden_concepts
312
323
  ]
@@ -316,11 +327,11 @@ class StrategyNode:
316
327
  return "\t" * self.depth
317
328
 
318
329
  @property
319
- def all_concepts(self) -> list[Concept]:
330
+ def all_concepts(self) -> list[BuildConcept]:
320
331
  return [*self.output_concepts]
321
332
 
322
333
  @property
323
- def all_used_concepts(self) -> list[Concept]:
334
+ def all_used_concepts(self) -> list[BuildConcept]:
324
335
  return [*self.input_concepts, *self.existence_concepts]
325
336
 
326
337
  def __repr__(self):
@@ -333,11 +344,13 @@ class StrategyNode:
333
344
  return f"{self.__class__.__name__}<{contents}>"
334
345
 
335
346
  def _resolve(self) -> QueryDatasource:
336
- parent_sources: List[QueryDatasource | Datasource] = [
347
+ parent_sources: List[QueryDatasource | BuildDatasource] = [
337
348
  p.resolve() for p in self.parents
338
349
  ]
339
350
 
340
- grain = self.grain if self.grain else Grain.from_concepts(self.output_concepts)
351
+ grain = (
352
+ self.grain if self.grain else BuildGrain.from_concepts(self.output_concepts)
353
+ )
341
354
  source_map = resolve_concept_map(
342
355
  parent_sources,
343
356
  targets=self.output_concepts,
@@ -357,11 +370,12 @@ class StrategyNode:
357
370
  nullable_concepts=self.nullable_concepts,
358
371
  force_group=self.force_group,
359
372
  hidden_concepts=self.hidden_concepts,
373
+ ordering=self.ordering,
360
374
  )
361
375
 
362
376
  def rebuild_cache(self) -> QueryDatasource:
363
377
  self.tainted = True
364
- self.output_lcl = LooseConceptList(concepts=self.output_concepts)
378
+ self.output_lcl = LooseBuildConceptList(concepts=self.output_concepts)
365
379
  if not self.resolution_cache:
366
380
  return self.resolve()
367
381
  self.resolution_cache = None
@@ -388,9 +402,10 @@ class StrategyNode:
388
402
  preexisting_conditions=self.preexisting_conditions,
389
403
  force_group=self.force_group,
390
404
  grain=self.grain,
391
- hidden_concepts=list(self.hidden_concepts),
405
+ hidden_concepts=set(self.hidden_concepts),
392
406
  existence_concepts=list(self.existence_concepts),
393
407
  virtual_output_concepts=list(self.virtual_output_concepts),
408
+ ordering=self.ordering,
394
409
  )
395
410
 
396
411
 
@@ -398,12 +413,14 @@ class StrategyNode:
398
413
  class NodeJoin:
399
414
  left_node: StrategyNode
400
415
  right_node: StrategyNode
401
- concepts: List[Concept]
416
+ concepts: List[BuildConcept]
402
417
  join_type: JoinType
403
418
  filter_to_mutual: bool = False
404
419
  concept_pairs: list[ConceptPair] | None = None
405
420
 
406
421
  def __post_init__(self):
422
+ if self.left_node == self.right_node:
423
+ raise SyntaxError("Invalid join, left and right nodes are the same")
407
424
  if self.concept_pairs:
408
425
  return
409
426
  final_concepts = []
@@ -424,9 +441,7 @@ class NodeJoin:
424
441
  # if one datasource only has constants
425
442
  # we can join on 1=1
426
443
  for ds in [self.left_node, self.right_node]:
427
- if all(
428
- [c.derivation == PurposeLineage.CONSTANT for c in ds.all_concepts]
429
- ):
444
+ if all([c.derivation == Derivation.CONSTANT for c in ds.all_concepts]):
430
445
  self.concepts = []
431
446
  return
432
447
 
@@ -1,13 +1,15 @@
1
1
  from typing import List
2
2
 
3
- from trilogy.core.models import (
4
- Comparison,
5
- Concept,
6
- Conditional,
7
- Grain,
8
- Parenthetical,
3
+ from trilogy.core.enums import (
9
4
  SourceType,
10
5
  )
6
+ from trilogy.core.models.build import (
7
+ BuildComparison,
8
+ BuildConcept,
9
+ BuildConditional,
10
+ BuildGrain,
11
+ BuildParenthetical,
12
+ )
11
13
  from trilogy.core.processing.nodes.base_node import StrategyNode
12
14
 
13
15
 
@@ -24,18 +26,22 @@ class FilterNode(StrategyNode):
24
26
 
25
27
  def __init__(
26
28
  self,
27
- input_concepts: List[Concept],
28
- output_concepts: List[Concept],
29
+ input_concepts: List[BuildConcept],
30
+ output_concepts: List[BuildConcept],
29
31
  environment,
30
32
  whole_grain: bool = False,
31
33
  parents: List["StrategyNode"] | None = None,
32
34
  depth: int = 0,
33
- conditions: Conditional | Comparison | Parenthetical | None = None,
34
- preexisting_conditions: Conditional | Comparison | Parenthetical | None = None,
35
- partial_concepts: List[Concept] | None = None,
35
+ conditions: (
36
+ BuildConditional | BuildComparison | BuildParenthetical | None
37
+ ) = None,
38
+ preexisting_conditions: (
39
+ BuildConditional | BuildComparison | BuildParenthetical | None
40
+ ) = None,
41
+ partial_concepts: List[BuildConcept] | None = None,
36
42
  force_group: bool | None = False,
37
- grain: Grain | None = None,
38
- existence_concepts: List[Concept] | None = None,
43
+ grain: BuildGrain | None = None,
44
+ existence_concepts: List[BuildConcept] | None = None,
39
45
  ):
40
46
  super().__init__(
41
47
  output_concepts=output_concepts,