pytrilogy 0.0.2.48__py3-none-any.whl → 0.0.2.50__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 (38) hide show
  1. {pytrilogy-0.0.2.48.dist-info → pytrilogy-0.0.2.50.dist-info}/METADATA +1 -1
  2. {pytrilogy-0.0.2.48.dist-info → pytrilogy-0.0.2.50.dist-info}/RECORD +38 -38
  3. trilogy/__init__.py +1 -1
  4. trilogy/core/enums.py +11 -0
  5. trilogy/core/functions.py +4 -1
  6. trilogy/core/models.py +29 -14
  7. trilogy/core/processing/concept_strategies_v3.py +3 -3
  8. trilogy/core/processing/node_generators/common.py +0 -2
  9. trilogy/core/processing/node_generators/filter_node.py +0 -3
  10. trilogy/core/processing/node_generators/group_node.py +0 -1
  11. trilogy/core/processing/node_generators/group_to_node.py +0 -2
  12. trilogy/core/processing/node_generators/multiselect_node.py +0 -2
  13. trilogy/core/processing/node_generators/node_merge_node.py +0 -1
  14. trilogy/core/processing/node_generators/rowset_node.py +27 -8
  15. trilogy/core/processing/node_generators/select_merge_node.py +138 -59
  16. trilogy/core/processing/node_generators/union_node.py +0 -1
  17. trilogy/core/processing/node_generators/unnest_node.py +0 -2
  18. trilogy/core/processing/node_generators/window_node.py +0 -2
  19. trilogy/core/processing/nodes/base_node.py +28 -3
  20. trilogy/core/processing/nodes/filter_node.py +0 -3
  21. trilogy/core/processing/nodes/group_node.py +9 -6
  22. trilogy/core/processing/nodes/merge_node.py +3 -4
  23. trilogy/core/processing/nodes/select_node_v2.py +5 -4
  24. trilogy/core/processing/nodes/union_node.py +0 -3
  25. trilogy/core/processing/nodes/unnest_node.py +0 -3
  26. trilogy/core/processing/nodes/window_node.py +0 -3
  27. trilogy/core/processing/utility.py +4 -1
  28. trilogy/core/query_processor.py +3 -8
  29. trilogy/dialect/base.py +14 -2
  30. trilogy/dialect/duckdb.py +7 -0
  31. trilogy/hooks/graph_hook.py +14 -0
  32. trilogy/parsing/common.py +14 -5
  33. trilogy/parsing/parse_engine.py +32 -0
  34. trilogy/parsing/trilogy.lark +3 -1
  35. {pytrilogy-0.0.2.48.dist-info → pytrilogy-0.0.2.50.dist-info}/LICENSE.md +0 -0
  36. {pytrilogy-0.0.2.48.dist-info → pytrilogy-0.0.2.50.dist-info}/WHEEL +0 -0
  37. {pytrilogy-0.0.2.48.dist-info → pytrilogy-0.0.2.50.dist-info}/entry_points.txt +0 -0
  38. {pytrilogy-0.0.2.48.dist-info → pytrilogy-0.0.2.50.dist-info}/top_level.txt +0 -0
@@ -13,6 +13,9 @@ from trilogy.core.models import (
13
13
  LooseConceptList,
14
14
  WhereClause,
15
15
  )
16
+ from trilogy.core.processing.node_generators.select_helpers.datasource_injection import (
17
+ get_union_sources,
18
+ )
16
19
  from trilogy.core.processing.nodes import (
17
20
  ConstantNode,
18
21
  GroupNode,
@@ -35,38 +38,66 @@ def extract_address(node: str):
35
38
  def get_graph_partial_nodes(
36
39
  g: nx.DiGraph, conditions: WhereClause | None
37
40
  ) -> dict[str, list[str]]:
38
- datasources: dict[str, Datasource] = nx.get_node_attributes(g, "datasource")
41
+ datasources: dict[str, Datasource | list[Datasource]] = nx.get_node_attributes(
42
+ g, "datasource"
43
+ )
39
44
  partial: dict[str, list[str]] = {}
40
45
  for node in g.nodes:
41
46
  if node in datasources:
42
47
  ds = datasources[node]
43
- partial[node] = [concept_to_node(c) for c in ds.partial_concepts]
44
- if ds.non_partial_for and conditions == ds.non_partial_for:
48
+ if not isinstance(ds, list):
49
+ if ds.non_partial_for and conditions == ds.non_partial_for:
50
+ partial[node] = []
51
+ continue
52
+ partial[node] = [concept_to_node(c) for c in ds.partial_concepts]
53
+ ds = [ds]
54
+ # assume union sources have no partial
55
+ else:
45
56
  partial[node] = []
46
57
 
47
58
  return partial
48
59
 
49
60
 
50
61
  def get_graph_grain_length(g: nx.DiGraph) -> dict[str, int]:
51
- datasources: dict[str, Datasource] = nx.get_node_attributes(g, "datasource")
52
- partial: dict[str, int] = {}
62
+ datasources: dict[str, Datasource | list[Datasource]] = nx.get_node_attributes(
63
+ g, "datasource"
64
+ )
65
+ grain_length: dict[str, int] = {}
53
66
  for node in g.nodes:
54
67
  if node in datasources:
55
- partial[node] = len(datasources[node].grain.components)
56
- return partial
68
+ lookup = datasources[node]
69
+ if not isinstance(lookup, list):
70
+ lookup = [lookup]
71
+ assert isinstance(lookup, list)
72
+ grain_length[node] = sum(len(x.grain.components) for x in lookup)
73
+ return grain_length
57
74
 
58
75
 
59
76
  def create_pruned_concept_graph(
60
77
  g: nx.DiGraph,
61
78
  all_concepts: List[Concept],
79
+ datasources: list[Datasource],
62
80
  accept_partial: bool = False,
63
81
  conditions: WhereClause | None = None,
64
82
  ) -> nx.DiGraph:
65
83
  orig_g = g
66
84
  g = g.copy()
85
+
86
+ union_options = get_union_sources(datasources, all_concepts)
87
+ for ds_list in union_options:
88
+ node_address = "ds~" + "-".join([x.name for x in ds_list])
89
+ common: set[Concept] = set.intersection(
90
+ *[set(x.output_concepts) for x in ds_list]
91
+ )
92
+ g.add_node(node_address, datasource=ds_list)
93
+ for c in common:
94
+ g.add_edge(node_address, concept_to_node(c))
95
+
67
96
  target_addresses = set([c.address for c in all_concepts])
68
97
  concepts: dict[str, Concept] = nx.get_node_attributes(orig_g, "concept")
69
- datasources: dict[str, Datasource] = nx.get_node_attributes(orig_g, "datasource")
98
+ datasource_map: dict[str, Datasource | list[Datasource]] = nx.get_node_attributes(
99
+ orig_g, "datasource"
100
+ )
70
101
  relevant_concepts_pre = {
71
102
  n: x.address
72
103
  for n in g.nodes()
@@ -81,13 +112,13 @@ def create_pruned_concept_graph(
81
112
  to_remove = []
82
113
  for edge in g.edges:
83
114
  if (
84
- edge[0] in datasources
115
+ edge[0] in datasource_map
85
116
  and (pnodes := partial.get(edge[0], []))
86
117
  and edge[1] in pnodes
87
118
  ):
88
119
  to_remove.append(edge)
89
120
  if (
90
- edge[1] in datasources
121
+ edge[1] in datasource_map
91
122
  and (pnodes := partial.get(edge[1], []))
92
123
  and edge[0] in pnodes
93
124
  ):
@@ -136,7 +167,9 @@ def create_pruned_concept_graph(
136
167
  for edge in orig_g.edges():
137
168
  if edge[0] in relevant and edge[1] in relevant:
138
169
  g.add_edge(edge[0], edge[1])
139
-
170
+ # if we have no ds nodes at all, for non constant, we can't find it
171
+ if not any([n.startswith("ds~") for n in g.nodes]):
172
+ return None
140
173
  return g
141
174
 
142
175
 
@@ -190,6 +223,54 @@ def resolve_subgraphs(
190
223
  return pruned_subgraphs
191
224
 
192
225
 
226
+ def create_datasource_node(
227
+ datasource: Datasource,
228
+ all_concepts: List[Concept],
229
+ accept_partial: bool,
230
+ environment: Environment,
231
+ depth: int,
232
+ conditions: WhereClause | None = None,
233
+ ) -> tuple[StrategyNode, bool]:
234
+ target_grain = Grain(components=all_concepts)
235
+ force_group = False
236
+ if not datasource.grain.issubset(target_grain):
237
+ force_group = True
238
+ partial_concepts = [
239
+ c.concept
240
+ for c in datasource.columns
241
+ if not c.is_complete and c.concept.address in all_concepts
242
+ ]
243
+ partial_lcl = LooseConceptList(concepts=partial_concepts)
244
+ nullable_concepts = [
245
+ c.concept
246
+ for c in datasource.columns
247
+ if c.is_nullable and c.concept.address in all_concepts
248
+ ]
249
+ nullable_lcl = LooseConceptList(concepts=nullable_concepts)
250
+ partial_is_full = conditions and (conditions == datasource.non_partial_for)
251
+ return (
252
+ SelectNode(
253
+ input_concepts=[c.concept for c in datasource.columns],
254
+ output_concepts=all_concepts,
255
+ environment=environment,
256
+ parents=[],
257
+ depth=depth,
258
+ partial_concepts=(
259
+ [] if partial_is_full else [c for c in all_concepts if c in partial_lcl]
260
+ ),
261
+ nullable_concepts=[c for c in all_concepts if c in nullable_lcl],
262
+ accept_partial=accept_partial,
263
+ datasource=datasource,
264
+ grain=Grain(components=all_concepts),
265
+ conditions=datasource.where.conditional if datasource.where else None,
266
+ preexisting_conditions=(
267
+ conditions.conditional if partial_is_full and conditions else None
268
+ ),
269
+ ),
270
+ force_group,
271
+ )
272
+
273
+
193
274
  def create_select_node(
194
275
  ds_name: str,
195
276
  subgraph: list[str],
@@ -199,12 +280,11 @@ def create_select_node(
199
280
  depth: int,
200
281
  conditions: WhereClause | None = None,
201
282
  ) -> StrategyNode:
202
- ds_name = ds_name.split("~")[1]
283
+
203
284
  all_concepts = [
204
285
  environment.concepts[extract_address(c)] for c in subgraph if c.startswith("c~")
205
286
  ]
206
287
 
207
- all_lcl = LooseConceptList(concepts=all_concepts)
208
288
  if all([c.derivation == PurposeLineage.CONSTANT for c in all_concepts]):
209
289
  logger.info(
210
290
  f"{padding(depth)}{LOGGER_PREFIX} All concepts {[x.address for x in all_concepts]} are constants, returning constant node"
@@ -213,7 +293,6 @@ def create_select_node(
213
293
  output_concepts=all_concepts,
214
294
  input_concepts=[],
215
295
  environment=environment,
216
- g=g,
217
296
  parents=[],
218
297
  depth=depth,
219
298
  # no partial for constants
@@ -221,41 +300,44 @@ def create_select_node(
221
300
  force_group=False,
222
301
  )
223
302
 
224
- datasource = environment.datasources[ds_name]
225
- target_grain = Grain(components=all_concepts)
226
- force_group = False
227
- if not datasource.grain.issubset(target_grain):
228
- force_group = True
229
- partial_concepts = [
230
- c.concept
231
- for c in datasource.columns
232
- if not c.is_complete and c.concept in all_lcl
233
- ]
234
- partial_lcl = LooseConceptList(concepts=partial_concepts)
235
- nullable_concepts = [
236
- c.concept for c in datasource.columns if c.is_nullable and c.concept in all_lcl
237
- ]
238
- nullable_lcl = LooseConceptList(concepts=nullable_concepts)
239
- partial_is_full = conditions and (conditions == datasource.non_partial_for)
240
- bcandidate: StrategyNode = SelectNode(
241
- input_concepts=[c.concept for c in datasource.columns],
242
- output_concepts=all_concepts,
243
- environment=environment,
244
- g=g,
245
- parents=[],
246
- depth=depth,
247
- partial_concepts=(
248
- [] if partial_is_full else [c for c in all_concepts if c in partial_lcl]
249
- ),
250
- nullable_concepts=[c for c in all_concepts if c in nullable_lcl],
251
- accept_partial=accept_partial,
252
- datasource=datasource,
253
- grain=Grain(components=all_concepts),
254
- conditions=datasource.where.conditional if datasource.where else None,
255
- preexisting_conditions=(
256
- conditions.conditional if partial_is_full and conditions else None
257
- ),
258
- )
303
+ datasource: dict[str, Datasource | list[Datasource]] = nx.get_node_attributes(
304
+ g, "datasource"
305
+ )[ds_name]
306
+ if isinstance(datasource, Datasource):
307
+ bcandidate, force_group = create_datasource_node(
308
+ datasource,
309
+ all_concepts,
310
+ accept_partial,
311
+ environment,
312
+ depth,
313
+ conditions=conditions,
314
+ )
315
+
316
+ elif isinstance(datasource, list):
317
+ from trilogy.core.processing.nodes.union_node import UnionNode
318
+
319
+ force_group = False
320
+ parents = []
321
+ for x in datasource:
322
+ subnode, fg = create_datasource_node(
323
+ x,
324
+ all_concepts,
325
+ accept_partial,
326
+ environment,
327
+ depth,
328
+ conditions=conditions,
329
+ )
330
+ parents.append(subnode)
331
+ force_group = force_group or fg
332
+ bcandidate = UnionNode(
333
+ output_concepts=all_concepts,
334
+ input_concepts=all_concepts,
335
+ environment=environment,
336
+ parents=parents,
337
+ depth=depth,
338
+ )
339
+ else:
340
+ raise ValueError(f"Unknown datasource type {datasource}")
259
341
 
260
342
  # we need to nest the group node one further
261
343
  if force_group is True:
@@ -263,14 +345,11 @@ def create_select_node(
263
345
  output_concepts=all_concepts,
264
346
  input_concepts=all_concepts,
265
347
  environment=environment,
266
- g=g,
267
348
  parents=[bcandidate],
268
349
  depth=depth,
269
350
  partial_concepts=bcandidate.partial_concepts,
270
351
  nullable_concepts=bcandidate.nullable_concepts,
271
- preexisting_conditions=(
272
- conditions.conditional if partial_is_full and conditions else None
273
- ),
352
+ preexisting_conditions=bcandidate.preexisting_conditions,
274
353
  )
275
354
  else:
276
355
  candidate = bcandidate
@@ -292,7 +371,6 @@ def gen_select_merge_node(
292
371
  output_concepts=constants,
293
372
  input_concepts=[],
294
373
  environment=environment,
295
- g=g,
296
374
  parents=[],
297
375
  depth=depth,
298
376
  partial_concepts=[],
@@ -300,7 +378,11 @@ def gen_select_merge_node(
300
378
  )
301
379
  for attempt in [False, True]:
302
380
  pruned_concept_graph = create_pruned_concept_graph(
303
- g, non_constant, attempt, conditions
381
+ g,
382
+ non_constant,
383
+ accept_partial=attempt,
384
+ conditions=conditions,
385
+ datasources=list(environment.datasources.values()),
304
386
  )
305
387
  if pruned_concept_graph:
306
388
  logger.info(
@@ -321,7 +403,7 @@ def gen_select_merge_node(
321
403
  create_select_node(
322
404
  k,
323
405
  subgraph,
324
- g=g,
406
+ g=pruned_concept_graph,
325
407
  accept_partial=accept_partial,
326
408
  environment=environment,
327
409
  depth=depth,
@@ -338,7 +420,6 @@ def gen_select_merge_node(
338
420
  output_concepts=constants,
339
421
  input_concepts=[],
340
422
  environment=environment,
341
- g=g,
342
423
  parents=[],
343
424
  depth=depth,
344
425
  partial_concepts=[],
@@ -361,7 +442,6 @@ def gen_select_merge_node(
361
442
  output_concepts=all_concepts,
362
443
  input_concepts=non_constant,
363
444
  environment=environment,
364
- g=g,
365
445
  depth=depth,
366
446
  parents=parents,
367
447
  preexisting_conditions=preexisting_conditions,
@@ -372,7 +452,6 @@ def gen_select_merge_node(
372
452
  output_concepts=all_concepts,
373
453
  input_concepts=all_concepts,
374
454
  environment=environment,
375
- g=g,
376
455
  parents=[base],
377
456
  depth=depth,
378
457
  preexisting_conditions=preexisting_conditions,
@@ -70,6 +70,5 @@ def gen_union_node(
70
70
  input_concepts=[concept] + local_optional,
71
71
  output_concepts=[concept] + local_optional,
72
72
  environment=environment,
73
- g=g,
74
73
  parents=parents,
75
74
  )
@@ -46,7 +46,6 @@ def gen_unnest_node(
46
46
  input_concepts=arguments + non_equivalent_optional,
47
47
  output_concepts=[concept] + local_optional,
48
48
  environment=environment,
49
- g=g,
50
49
  parents=([parent] if (arguments or local_optional) else []),
51
50
  )
52
51
  # we need to sometimes nest an unnest node,
@@ -56,7 +55,6 @@ def gen_unnest_node(
56
55
  input_concepts=base.output_concepts,
57
56
  output_concepts=base.output_concepts,
58
57
  environment=environment,
59
- g=g,
60
58
  parents=[base],
61
59
  preexisting_conditions=conditions.conditional if conditions else None,
62
60
  )
@@ -86,7 +86,6 @@ def gen_window_node(
86
86
  input_concepts=parent_concepts + targets + non_equivalent_optional,
87
87
  output_concepts=[concept] + parent_concepts + local_optional,
88
88
  environment=environment,
89
- g=g,
90
89
  parents=[
91
90
  parent_node,
92
91
  ],
@@ -98,7 +97,6 @@ def gen_window_node(
98
97
  input_concepts=[concept] + local_optional,
99
98
  output_concepts=[concept] + local_optional,
100
99
  environment=environment,
101
- g=g,
102
100
  parents=[_window_node],
103
101
  preexisting_conditions=conditions.conditional if conditions else None,
104
102
  )
@@ -156,7 +156,6 @@ class StrategyNode:
156
156
  input_concepts: List[Concept],
157
157
  output_concepts: List[Concept],
158
158
  environment: Environment,
159
- g,
160
159
  whole_grain: bool = False,
161
160
  parents: List["StrategyNode"] | None = None,
162
161
  partial_concepts: List[Concept] | None = None,
@@ -178,7 +177,6 @@ class StrategyNode:
178
177
  self.output_lcl = LooseConceptList(concepts=self.output_concepts)
179
178
 
180
179
  self.environment = environment
181
- self.g = g
182
180
  self.whole_grain = whole_grain
183
181
  self.parents = parents or []
184
182
  self.resolution_cache: Optional[QueryDatasource] = None
@@ -211,8 +209,22 @@ class StrategyNode:
211
209
  operator=BooleanOperator.AND,
212
210
  )
213
211
  self.validate_parents()
212
+ self.validate_inputs()
214
213
  self.log = True
215
214
 
215
+ def validate_inputs(self):
216
+ if not self.parents:
217
+ return
218
+ non_hidden = set()
219
+ for x in self.parents:
220
+ for z in x.usable_outputs:
221
+ non_hidden.add(z.address)
222
+ if not all([x.address in non_hidden for x in self.input_concepts]):
223
+ missing = [x for x in self.input_concepts if x.address not in non_hidden]
224
+ raise ValueError(
225
+ f"Invalid input concepts; {missing} are missing non-hidden parent nodes"
226
+ )
227
+
216
228
  def add_parents(self, parents: list["StrategyNode"]):
217
229
  self.parents += parents
218
230
  self.validate_parents()
@@ -288,6 +300,14 @@ class StrategyNode:
288
300
  self.rebuild_cache()
289
301
  return self
290
302
 
303
+ def unhide_output_concepts(self, concepts: List[Concept], rebuild: bool = True):
304
+ self.hidden_concepts = [
305
+ x for x in self.hidden_concepts if x.address not in concepts
306
+ ]
307
+ if rebuild:
308
+ self.rebuild_cache()
309
+ return self
310
+
291
311
  def remove_output_concepts(self, concepts: List[Concept], rebuild: bool = True):
292
312
  for x in concepts:
293
313
  self.hidden_concepts.append(x)
@@ -299,6 +319,12 @@ class StrategyNode:
299
319
  self.rebuild_cache()
300
320
  return self
301
321
 
322
+ @property
323
+ def usable_outputs(self) -> list[Concept]:
324
+ return [
325
+ x for x in self.output_concepts if x.address not in self.hidden_concepts
326
+ ]
327
+
302
328
  @property
303
329
  def logging_prefix(self) -> str:
304
330
  return "\t" * self.depth
@@ -371,7 +397,6 @@ class StrategyNode:
371
397
  input_concepts=list(self.input_concepts),
372
398
  output_concepts=list(self.output_concepts),
373
399
  environment=self.environment,
374
- g=self.g,
375
400
  whole_grain=self.whole_grain,
376
401
  parents=list(self.parents),
377
402
  partial_concepts=list(self.partial_concepts),
@@ -27,7 +27,6 @@ class FilterNode(StrategyNode):
27
27
  input_concepts: List[Concept],
28
28
  output_concepts: List[Concept],
29
29
  environment,
30
- g,
31
30
  whole_grain: bool = False,
32
31
  parents: List["StrategyNode"] | None = None,
33
32
  depth: int = 0,
@@ -41,7 +40,6 @@ class FilterNode(StrategyNode):
41
40
  super().__init__(
42
41
  output_concepts=output_concepts,
43
42
  environment=environment,
44
- g=g,
45
43
  whole_grain=whole_grain,
46
44
  parents=parents,
47
45
  depth=depth,
@@ -59,7 +57,6 @@ class FilterNode(StrategyNode):
59
57
  input_concepts=list(self.input_concepts),
60
58
  output_concepts=list(self.output_concepts),
61
59
  environment=self.environment,
62
- g=self.g,
63
60
  whole_grain=self.whole_grain,
64
61
  parents=self.parents,
65
62
  depth=self.depth,
@@ -32,7 +32,6 @@ class GroupNode(StrategyNode):
32
32
  output_concepts: List[Concept],
33
33
  input_concepts: List[Concept],
34
34
  environment: Environment,
35
- g,
36
35
  whole_grain: bool = False,
37
36
  parents: List["StrategyNode"] | None = None,
38
37
  depth: int = 0,
@@ -48,7 +47,6 @@ class GroupNode(StrategyNode):
48
47
  input_concepts=input_concepts,
49
48
  output_concepts=output_concepts,
50
49
  environment=environment,
51
- g=g,
52
50
  whole_grain=whole_grain,
53
51
  parents=parents,
54
52
  depth=depth,
@@ -105,9 +103,9 @@ class GroupNode(StrategyNode):
105
103
  logger.info(
106
104
  f"{self.logging_prefix}{LOGGER_PREFIX} Parent node"
107
105
  f" {[c.address for c in parent.output_concepts[:2]]}... has"
108
- " grain"
106
+ " set node grain"
109
107
  f" {parent.grain}"
110
- f" resolved grain {parent.resolve().grain}"
108
+ f" and resolved grain {parent.resolve().grain}"
111
109
  f" {type(parent)}"
112
110
  )
113
111
  source_type = SourceType.GROUP
@@ -146,7 +144,13 @@ class GroupNode(StrategyNode):
146
144
  # inject an additional CTE
147
145
  if self.conditions and not is_scalar_condition(self.conditions):
148
146
  base.condition = None
149
- base.output_concepts = self.output_concepts + self.conditions.row_arguments
147
+ base.output_concepts = unique(
148
+ base.output_concepts + self.conditions.row_arguments, "address"
149
+ )
150
+ # re-visible any hidden concepts
151
+ base.hidden_concepts = [
152
+ x for x in base.hidden_concepts if x not in base.output_concepts
153
+ ]
150
154
  source_map = resolve_concept_map(
151
155
  [base],
152
156
  targets=self.output_concepts,
@@ -172,7 +176,6 @@ class GroupNode(StrategyNode):
172
176
  input_concepts=list(self.input_concepts),
173
177
  output_concepts=list(self.output_concepts),
174
178
  environment=self.environment,
175
- g=self.g,
176
179
  whole_grain=self.whole_grain,
177
180
  parents=self.parents,
178
181
  depth=self.depth,
@@ -103,7 +103,6 @@ class MergeNode(StrategyNode):
103
103
  input_concepts: List[Concept],
104
104
  output_concepts: List[Concept],
105
105
  environment,
106
- g,
107
106
  whole_grain: bool = False,
108
107
  parents: List["StrategyNode"] | None = None,
109
108
  node_joins: List[NodeJoin] | None = None,
@@ -124,7 +123,6 @@ class MergeNode(StrategyNode):
124
123
  input_concepts=input_concepts,
125
124
  output_concepts=output_concepts,
126
125
  environment=environment,
127
- g=g,
128
126
  whole_grain=whole_grain,
129
127
  parents=parents,
130
128
  depth=depth,
@@ -330,8 +328,9 @@ class MergeNode(StrategyNode):
330
328
  force_group = None
331
329
 
332
330
  qd_joins: List[BaseJoin | UnnestJoin] = [*joins]
331
+
333
332
  source_map = resolve_concept_map(
334
- list(merged.values()),
333
+ final_datasets,
335
334
  targets=self.output_concepts,
336
335
  inherited_inputs=self.input_concepts + self.existence_concepts,
337
336
  full_joins=full_join_concepts,
@@ -339,6 +338,7 @@ class MergeNode(StrategyNode):
339
338
  nullable_concepts = find_nullable_concepts(
340
339
  source_map=source_map, joins=joins, datasources=final_datasets
341
340
  )
341
+
342
342
  qds = QueryDatasource(
343
343
  input_concepts=unique(self.input_concepts, "address"),
344
344
  output_concepts=unique(self.output_concepts, "address"),
@@ -362,7 +362,6 @@ class MergeNode(StrategyNode):
362
362
  input_concepts=list(self.input_concepts),
363
363
  output_concepts=list(self.output_concepts),
364
364
  environment=self.environment,
365
- g=self.g,
366
365
  whole_grain=self.whole_grain,
367
366
  parents=self.parents,
368
367
  depth=self.depth,
@@ -34,7 +34,6 @@ class SelectNode(StrategyNode):
34
34
  input_concepts: List[Concept],
35
35
  output_concepts: List[Concept],
36
36
  environment: Environment,
37
- g,
38
37
  datasource: Datasource | None = None,
39
38
  whole_grain: bool = False,
40
39
  parents: List["StrategyNode"] | None = None,
@@ -52,7 +51,6 @@ class SelectNode(StrategyNode):
52
51
  input_concepts=input_concepts,
53
52
  output_concepts=output_concepts,
54
53
  environment=environment,
55
- g=g,
56
54
  whole_grain=whole_grain,
57
55
  parents=parents,
58
56
  depth=depth,
@@ -67,6 +65,11 @@ class SelectNode(StrategyNode):
67
65
  self.accept_partial = accept_partial
68
66
  self.datasource = datasource
69
67
 
68
+ def validate_inputs(self):
69
+ # we do not need to validate inputs for a select node
70
+ # as it will be a root
71
+ return
72
+
70
73
  def resolve_from_provided_datasource(
71
74
  self,
72
75
  ) -> QueryDatasource:
@@ -192,7 +195,6 @@ class SelectNode(StrategyNode):
192
195
  input_concepts=list(self.input_concepts),
193
196
  output_concepts=list(self.output_concepts),
194
197
  environment=self.environment,
195
- g=self.g,
196
198
  datasource=self.datasource,
197
199
  depth=self.depth,
198
200
  parents=self.parents,
@@ -216,7 +218,6 @@ class ConstantNode(SelectNode):
216
218
  input_concepts=list(self.input_concepts),
217
219
  output_concepts=list(self.output_concepts),
218
220
  environment=self.environment,
219
- g=self.g,
220
221
  datasource=self.datasource,
221
222
  depth=self.depth,
222
223
  partial_concepts=list(self.partial_concepts),
@@ -18,7 +18,6 @@ class UnionNode(StrategyNode):
18
18
  input_concepts: List[Concept],
19
19
  output_concepts: List[Concept],
20
20
  environment,
21
- g,
22
21
  whole_grain: bool = False,
23
22
  parents: List["StrategyNode"] | None = None,
24
23
  depth: int = 0,
@@ -27,7 +26,6 @@ class UnionNode(StrategyNode):
27
26
  input_concepts=input_concepts,
28
27
  output_concepts=output_concepts,
29
28
  environment=environment,
30
- g=g,
31
29
  whole_grain=whole_grain,
32
30
  parents=parents,
33
31
  depth=depth,
@@ -43,7 +41,6 @@ class UnionNode(StrategyNode):
43
41
  input_concepts=list(self.input_concepts),
44
42
  output_concepts=list(self.output_concepts),
45
43
  environment=self.environment,
46
- g=self.g,
47
44
  whole_grain=self.whole_grain,
48
45
  parents=self.parents,
49
46
  depth=self.depth,
@@ -23,7 +23,6 @@ class UnnestNode(StrategyNode):
23
23
  input_concepts: List[Concept],
24
24
  output_concepts: List[Concept],
25
25
  environment,
26
- g,
27
26
  whole_grain: bool = False,
28
27
  parents: List["StrategyNode"] | None = None,
29
28
  depth: int = 0,
@@ -32,7 +31,6 @@ class UnnestNode(StrategyNode):
32
31
  input_concepts=input_concepts,
33
32
  output_concepts=output_concepts,
34
33
  environment=environment,
35
- g=g,
36
34
  whole_grain=whole_grain,
37
35
  parents=parents,
38
36
  depth=depth,
@@ -62,7 +60,6 @@ class UnnestNode(StrategyNode):
62
60
  input_concepts=list(self.input_concepts),
63
61
  output_concepts=list(self.output_concepts),
64
62
  environment=self.environment,
65
- g=self.g,
66
63
  whole_grain=self.whole_grain,
67
64
  parents=self.parents,
68
65
  depth=self.depth,
@@ -12,7 +12,6 @@ class WindowNode(StrategyNode):
12
12
  input_concepts: List[Concept],
13
13
  output_concepts: List[Concept],
14
14
  environment,
15
- g,
16
15
  whole_grain: bool = False,
17
16
  parents: List["StrategyNode"] | None = None,
18
17
  depth: int = 0,
@@ -21,7 +20,6 @@ class WindowNode(StrategyNode):
21
20
  input_concepts=input_concepts,
22
21
  output_concepts=output_concepts,
23
22
  environment=environment,
24
- g=g,
25
23
  whole_grain=whole_grain,
26
24
  parents=parents,
27
25
  depth=depth,
@@ -36,7 +34,6 @@ class WindowNode(StrategyNode):
36
34
  input_concepts=list(self.input_concepts),
37
35
  output_concepts=list(self.output_concepts),
38
36
  environment=self.environment,
39
- g=self.g,
40
37
  whole_grain=self.whole_grain,
41
38
  parents=self.parents,
42
39
  depth=self.depth,