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.
- {pytrilogy-0.0.2.48.dist-info → pytrilogy-0.0.2.50.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.2.48.dist-info → pytrilogy-0.0.2.50.dist-info}/RECORD +38 -38
- trilogy/__init__.py +1 -1
- trilogy/core/enums.py +11 -0
- trilogy/core/functions.py +4 -1
- trilogy/core/models.py +29 -14
- trilogy/core/processing/concept_strategies_v3.py +3 -3
- trilogy/core/processing/node_generators/common.py +0 -2
- trilogy/core/processing/node_generators/filter_node.py +0 -3
- trilogy/core/processing/node_generators/group_node.py +0 -1
- trilogy/core/processing/node_generators/group_to_node.py +0 -2
- trilogy/core/processing/node_generators/multiselect_node.py +0 -2
- trilogy/core/processing/node_generators/node_merge_node.py +0 -1
- trilogy/core/processing/node_generators/rowset_node.py +27 -8
- trilogy/core/processing/node_generators/select_merge_node.py +138 -59
- trilogy/core/processing/node_generators/union_node.py +0 -1
- trilogy/core/processing/node_generators/unnest_node.py +0 -2
- trilogy/core/processing/node_generators/window_node.py +0 -2
- trilogy/core/processing/nodes/base_node.py +28 -3
- trilogy/core/processing/nodes/filter_node.py +0 -3
- trilogy/core/processing/nodes/group_node.py +9 -6
- trilogy/core/processing/nodes/merge_node.py +3 -4
- trilogy/core/processing/nodes/select_node_v2.py +5 -4
- trilogy/core/processing/nodes/union_node.py +0 -3
- trilogy/core/processing/nodes/unnest_node.py +0 -3
- trilogy/core/processing/nodes/window_node.py +0 -3
- trilogy/core/processing/utility.py +4 -1
- trilogy/core/query_processor.py +3 -8
- trilogy/dialect/base.py +14 -2
- trilogy/dialect/duckdb.py +7 -0
- trilogy/hooks/graph_hook.py +14 -0
- trilogy/parsing/common.py +14 -5
- trilogy/parsing/parse_engine.py +32 -0
- trilogy/parsing/trilogy.lark +3 -1
- {pytrilogy-0.0.2.48.dist-info → pytrilogy-0.0.2.50.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.48.dist-info → pytrilogy-0.0.2.50.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.2.48.dist-info → pytrilogy-0.0.2.50.dist-info}/entry_points.txt +0 -0
- {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(
|
|
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
|
-
|
|
44
|
-
|
|
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(
|
|
52
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if
|
|
228
|
-
force_group =
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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,
|
|
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=
|
|
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,
|
|
@@ -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 =
|
|
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
|
-
|
|
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,
|