pytrilogy 0.0.2.58__py3-none-any.whl → 0.0.3.1__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 (76) hide show
  1. {pytrilogy-0.0.2.58.dist-info → pytrilogy-0.0.3.1.dist-info}/METADATA +9 -2
  2. pytrilogy-0.0.3.1.dist-info/RECORD +99 -0
  3. {pytrilogy-0.0.2.58.dist-info → pytrilogy-0.0.3.1.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 +1859 -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 +701 -0
  19. trilogy/core/models/execute.py +931 -0
  20. trilogy/core/optimization.py +14 -16
  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 +178 -145
  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 +50 -44
  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 +11 -8
  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 +55 -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 +18 -16
  41. trilogy/core/processing/nodes/__init__.py +21 -18
  42. trilogy/core/processing/nodes/base_node.py +82 -66
  43. trilogy/core/processing/nodes/filter_node.py +19 -13
  44. trilogy/core/processing/nodes/group_node.py +50 -35
  45. trilogy/core/processing/nodes/merge_node.py +45 -36
  46. trilogy/core/processing/nodes/select_node_v2.py +53 -39
  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 +103 -75
  51. trilogy/core/query_processor.py +70 -47
  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 +148 -106
  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/__init__.py +4 -0
  65. trilogy/hooks/base_hook.py +6 -4
  66. trilogy/hooks/query_debugger.py +113 -97
  67. trilogy/parser.py +1 -1
  68. trilogy/parsing/common.py +307 -64
  69. trilogy/parsing/parse_engine.py +277 -618
  70. trilogy/parsing/render.py +50 -26
  71. trilogy/scripts/trilogy.py +2 -1
  72. pytrilogy-0.0.2.58.dist-info/RECORD +0 -87
  73. trilogy/core/models.py +0 -4960
  74. {pytrilogy-0.0.2.58.dist-info → pytrilogy-0.0.3.1.dist-info}/LICENSE.md +0 -0
  75. {pytrilogy-0.0.2.58.dist-info → pytrilogy-0.0.3.1.dist-info}/entry_points.txt +0 -0
  76. {pytrilogy-0.0.2.58.dist-info → pytrilogy-0.0.3.1.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,41 @@ 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 = []
18
22
  if concept.lineage.over:
19
23
  base += concept.lineage.over
20
24
  if concept.lineage.order_by:
21
25
  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]
26
+ base += item.concept_arguments
28
27
  return concept.lineage.content, unique(base, "address")
29
28
 
30
29
 
31
30
  def gen_window_node(
32
- concept: Concept,
33
- local_optional: list[Concept],
34
- environment: Environment,
31
+ concept: BuildConcept,
32
+ local_optional: list[BuildConcept],
33
+ environment: BuildEnvironment,
35
34
  g,
36
35
  depth: int,
37
36
  source_concepts,
38
37
  history: History | None = None,
39
- conditions: WhereClause | None = None,
38
+ conditions: BuildWhereClause | None = None,
40
39
  ) -> StrategyNode | None:
41
40
  base, parent_concepts = resolve_window_parent_concepts(concept, environment)
41
+ logger.info(
42
+ f"{padding(depth)}{LOGGER_PREFIX} generating window node for {concept} with parents {parent_concepts}"
43
+ )
42
44
  equivalent_optional = [
43
45
  x
44
46
  for x in local_optional
45
- if isinstance(x.lineage, WindowItem)
47
+ if isinstance(x.lineage, WINDOW_TYPES)
46
48
  and resolve_window_parent_concepts(x, environment)[1] == parent_concepts
47
49
  ]
48
50
 
@@ -52,7 +54,7 @@ def gen_window_node(
52
54
  targets = [base]
53
55
  if equivalent_optional:
54
56
  for x in equivalent_optional:
55
- assert isinstance(x.lineage, WindowItem)
57
+ assert isinstance(x.lineage, WINDOW_TYPES)
56
58
  targets.append(x.lineage.content)
57
59
 
58
60
  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,41 +4,40 @@ 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
42
  if (
44
43
  isinstance(input, QueryDatasource)
@@ -76,8 +75,8 @@ def resolve_concept_map(
76
75
 
77
76
 
78
77
  def get_all_parent_partial(
79
- all_concepts: List[Concept], parents: List["StrategyNode"]
80
- ) -> List[Concept]:
78
+ all_concepts: List[BuildConcept], parents: List["StrategyNode"]
79
+ ) -> List[BuildConcept]:
81
80
  return unique(
82
81
  [
83
82
  c
@@ -103,8 +102,8 @@ def get_all_parent_partial(
103
102
 
104
103
 
105
104
  def get_all_parent_nullable(
106
- all_concepts: List[Concept], parents: List["StrategyNode"]
107
- ) -> List[Concept]:
105
+ all_concepts: List[BuildConcept], parents: List["StrategyNode"]
106
+ ) -> List[BuildConcept]:
108
107
  return unique(
109
108
  [
110
109
  c
@@ -127,28 +126,33 @@ class StrategyNode:
127
126
 
128
127
  def __init__(
129
128
  self,
130
- input_concepts: List[Concept],
131
- output_concepts: List[Concept],
132
- environment: Environment,
129
+ input_concepts: List[BuildConcept],
130
+ output_concepts: List[BuildConcept],
131
+ environment: BuildEnvironment,
133
132
  whole_grain: bool = False,
134
133
  parents: List["StrategyNode"] | None = None,
135
- partial_concepts: List[Concept] | None = None,
136
- nullable_concepts: List[Concept] | None = None,
134
+ partial_concepts: List[BuildConcept] | None = None,
135
+ nullable_concepts: List[BuildConcept] | None = None,
137
136
  depth: int = 0,
138
- conditions: Conditional | Comparison | Parenthetical | None = None,
139
- 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,
140
143
  force_group: bool | None = None,
141
- grain: Optional[Grain] = None,
144
+ grain: Optional[BuildGrain] = None,
142
145
  hidden_concepts: set[str] | None = None,
143
- existence_concepts: List[Concept] | None = None,
144
- virtual_output_concepts: List[Concept] | None = None,
146
+ existence_concepts: List[BuildConcept] | None = None,
147
+ virtual_output_concepts: List[BuildConcept] | None = None,
148
+ ordering: BuildOrderBy | None = None,
145
149
  ):
146
- self.input_concepts: List[Concept] = (
150
+ self.input_concepts: List[BuildConcept] = (
147
151
  unique(input_concepts, "address") if input_concepts else []
148
152
  )
149
- self.input_lcl = LooseConceptList(concepts=self.input_concepts)
150
- self.output_concepts: List[Concept] = unique(output_concepts, "address")
151
- 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)
152
156
 
153
157
  self.environment = environment
154
158
  self.whole_grain = whole_grain
@@ -160,7 +164,7 @@ class StrategyNode:
160
164
  self.nullable_concepts = nullable_concepts or get_all_parent_nullable(
161
165
  self.output_concepts, self.parents
162
166
  )
163
-
167
+ self.ordering = ordering
164
168
  self.depth = depth
165
169
  self.conditions = conditions
166
170
  self.grain = grain
@@ -177,7 +181,7 @@ class StrategyNode:
177
181
  and self.preexisting_conditions
178
182
  and self.conditions != self.preexisting_conditions
179
183
  ):
180
- self.preexisting_conditions = Conditional(
184
+ self.preexisting_conditions = BuildConditional(
181
185
  left=self.conditions,
182
186
  right=self.preexisting_conditions,
183
187
  operator=BooleanOperator.AND,
@@ -207,16 +211,18 @@ class StrategyNode:
207
211
  return self
208
212
 
209
213
  def set_preexisting_conditions(
210
- self, conditions: Conditional | Comparison | Parenthetical
214
+ self, conditions: BuildConditional | BuildComparison | BuildParenthetical
211
215
  ):
212
216
  self.preexisting_conditions = conditions
213
217
  return self
214
218
 
215
- def add_condition(self, condition: Conditional | Comparison | Parenthetical):
219
+ def add_condition(
220
+ self, condition: BuildConditional | BuildComparison | BuildParenthetical
221
+ ):
216
222
  if self.conditions and condition == self.conditions:
217
223
  return self
218
224
  if self.conditions:
219
- self.conditions = Conditional(
225
+ self.conditions = BuildConditional(
220
226
  left=self.conditions, right=condition, operator=BooleanOperator.AND
221
227
  )
222
228
  else:
@@ -238,27 +244,29 @@ class StrategyNode:
238
244
  self.output_concepts, self.parents
239
245
  )
240
246
 
241
- self.partial_lcl = LooseConceptList(concepts=self.partial_concepts)
247
+ self.partial_lcl = LooseBuildConceptList(concepts=self.partial_concepts)
242
248
 
243
- def add_output_concepts(self, concepts: List[Concept], rebuild: bool = True):
249
+ def add_output_concepts(self, concepts: List[BuildConcept], rebuild: bool = True):
244
250
  for concept in concepts:
245
251
  if concept.address not in self.output_lcl.addresses:
246
252
  self.output_concepts.append(concept)
247
- self.output_lcl = LooseConceptList(concepts=self.output_concepts)
253
+ self.output_lcl = LooseBuildConceptList(concepts=self.output_concepts)
248
254
  if rebuild:
249
255
  self.rebuild_cache()
250
256
  return self
251
257
 
252
- def add_partial_concepts(self, concepts: List[Concept], rebuild: bool = True):
258
+ def add_partial_concepts(self, concepts: List[BuildConcept], rebuild: bool = True):
253
259
  for concept in concepts:
254
260
  if concept.address not in self.partial_lcl.addresses:
255
261
  self.partial_concepts.append(concept)
256
- self.partial_lcl = LooseConceptList(concepts=self.partial_concepts)
262
+ self.partial_lcl = LooseBuildConceptList(concepts=self.partial_concepts)
257
263
  if rebuild:
258
264
  self.rebuild_cache()
259
265
  return self
260
266
 
261
- 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
+ ):
262
270
  for concept in concepts:
263
271
  if concept.address not in self.output_concepts:
264
272
  self.existence_concepts.append(concept)
@@ -266,34 +274,38 @@ class StrategyNode:
266
274
  self.rebuild_cache()
267
275
  return self
268
276
 
269
- def set_output_concepts(self, concepts: List[Concept], rebuild: bool = True):
277
+ def set_output_concepts(self, concepts: List[BuildConcept], rebuild: bool = True):
270
278
  # exit if no changes
271
279
  if self.output_concepts == concepts:
272
280
  return self
273
281
  self.output_concepts = concepts
274
- self.output_lcl = LooseConceptList(concepts=self.output_concepts)
282
+ self.output_lcl = LooseBuildConceptList(concepts=self.output_concepts)
275
283
 
276
284
  if rebuild:
277
285
  self.rebuild_cache()
278
286
  return self
279
287
 
280
- def add_output_concept(self, concept: Concept, rebuild: bool = True):
288
+ def add_output_concept(self, concept: BuildConcept, rebuild: bool = True):
281
289
  return self.add_output_concepts([concept], rebuild)
282
290
 
283
- def hide_output_concepts(self, concepts: List[Concept], rebuild: bool = True):
291
+ def hide_output_concepts(self, concepts: List[BuildConcept], rebuild: bool = True):
284
292
  for x in concepts:
285
293
  self.hidden_concepts.add(x.address)
286
294
  if rebuild:
287
295
  self.rebuild_cache()
288
296
  return self
289
297
 
290
- def unhide_output_concepts(self, concepts: List[Concept], rebuild: bool = True):
298
+ def unhide_output_concepts(
299
+ self, concepts: List[BuildConcept], rebuild: bool = True
300
+ ):
291
301
  self.hidden_concepts = set(x for x in self.hidden_concepts if x not in concepts)
292
302
  if rebuild:
293
303
  self.rebuild_cache()
294
304
  return self
295
305
 
296
- 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
+ ):
297
309
  for x in concepts:
298
310
  self.hidden_concepts.add(x.address)
299
311
  addresses = [x.address for x in concepts]
@@ -305,7 +317,7 @@ class StrategyNode:
305
317
  return self
306
318
 
307
319
  @property
308
- def usable_outputs(self) -> list[Concept]:
320
+ def usable_outputs(self) -> list[BuildConcept]:
309
321
  return [
310
322
  x for x in self.output_concepts if x.address not in self.hidden_concepts
311
323
  ]
@@ -315,11 +327,11 @@ class StrategyNode:
315
327
  return "\t" * self.depth
316
328
 
317
329
  @property
318
- def all_concepts(self) -> list[Concept]:
330
+ def all_concepts(self) -> list[BuildConcept]:
319
331
  return [*self.output_concepts]
320
332
 
321
333
  @property
322
- def all_used_concepts(self) -> list[Concept]:
334
+ def all_used_concepts(self) -> list[BuildConcept]:
323
335
  return [*self.input_concepts, *self.existence_concepts]
324
336
 
325
337
  def __repr__(self):
@@ -332,11 +344,13 @@ class StrategyNode:
332
344
  return f"{self.__class__.__name__}<{contents}>"
333
345
 
334
346
  def _resolve(self) -> QueryDatasource:
335
- parent_sources: List[QueryDatasource | Datasource] = [
347
+ parent_sources: List[QueryDatasource | BuildDatasource] = [
336
348
  p.resolve() for p in self.parents
337
349
  ]
338
350
 
339
- 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
+ )
340
354
  source_map = resolve_concept_map(
341
355
  parent_sources,
342
356
  targets=self.output_concepts,
@@ -356,11 +370,12 @@ class StrategyNode:
356
370
  nullable_concepts=self.nullable_concepts,
357
371
  force_group=self.force_group,
358
372
  hidden_concepts=self.hidden_concepts,
373
+ ordering=self.ordering,
359
374
  )
360
375
 
361
376
  def rebuild_cache(self) -> QueryDatasource:
362
377
  self.tainted = True
363
- self.output_lcl = LooseConceptList(concepts=self.output_concepts)
378
+ self.output_lcl = LooseBuildConceptList(concepts=self.output_concepts)
364
379
  if not self.resolution_cache:
365
380
  return self.resolve()
366
381
  self.resolution_cache = None
@@ -390,6 +405,7 @@ class StrategyNode:
390
405
  hidden_concepts=set(self.hidden_concepts),
391
406
  existence_concepts=list(self.existence_concepts),
392
407
  virtual_output_concepts=list(self.virtual_output_concepts),
408
+ ordering=self.ordering,
393
409
  )
394
410
 
395
411
 
@@ -397,12 +413,14 @@ class StrategyNode:
397
413
  class NodeJoin:
398
414
  left_node: StrategyNode
399
415
  right_node: StrategyNode
400
- concepts: List[Concept]
416
+ concepts: List[BuildConcept]
401
417
  join_type: JoinType
402
418
  filter_to_mutual: bool = False
403
419
  concept_pairs: list[ConceptPair] | None = None
404
420
 
405
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")
406
424
  if self.concept_pairs:
407
425
  return
408
426
  final_concepts = []
@@ -423,9 +441,7 @@ class NodeJoin:
423
441
  # if one datasource only has constants
424
442
  # we can join on 1=1
425
443
  for ds in [self.left_node, self.right_node]:
426
- if all(
427
- [c.derivation == PurposeLineage.CONSTANT for c in ds.all_concepts]
428
- ):
444
+ if all([c.derivation == Derivation.CONSTANT for c in ds.all_concepts]):
429
445
  self.concepts = []
430
446
  return
431
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,