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.
- {pytrilogy-0.0.2.58.dist-info → pytrilogy-0.0.3.1.dist-info}/METADATA +9 -2
- pytrilogy-0.0.3.1.dist-info/RECORD +99 -0
- {pytrilogy-0.0.2.58.dist-info → pytrilogy-0.0.3.1.dist-info}/WHEEL +1 -1
- trilogy/__init__.py +2 -2
- trilogy/core/enums.py +1 -7
- trilogy/core/env_processor.py +17 -5
- trilogy/core/environment_helpers.py +11 -25
- trilogy/core/exceptions.py +4 -0
- trilogy/core/functions.py +695 -261
- trilogy/core/graph_models.py +10 -10
- trilogy/core/internal.py +11 -2
- trilogy/core/models/__init__.py +0 -0
- trilogy/core/models/author.py +2110 -0
- trilogy/core/models/build.py +1859 -0
- trilogy/core/models/build_environment.py +151 -0
- trilogy/core/models/core.py +370 -0
- trilogy/core/models/datasource.py +297 -0
- trilogy/core/models/environment.py +701 -0
- trilogy/core/models/execute.py +931 -0
- trilogy/core/optimization.py +14 -16
- trilogy/core/optimizations/base_optimization.py +1 -1
- trilogy/core/optimizations/inline_constant.py +6 -6
- trilogy/core/optimizations/inline_datasource.py +17 -11
- trilogy/core/optimizations/predicate_pushdown.py +17 -16
- trilogy/core/processing/concept_strategies_v3.py +178 -145
- trilogy/core/processing/graph_utils.py +1 -1
- trilogy/core/processing/node_generators/basic_node.py +19 -18
- trilogy/core/processing/node_generators/common.py +50 -44
- trilogy/core/processing/node_generators/filter_node.py +26 -13
- trilogy/core/processing/node_generators/group_node.py +26 -21
- trilogy/core/processing/node_generators/group_to_node.py +11 -8
- trilogy/core/processing/node_generators/multiselect_node.py +60 -43
- trilogy/core/processing/node_generators/node_merge_node.py +76 -38
- trilogy/core/processing/node_generators/rowset_node.py +55 -36
- trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +27 -34
- trilogy/core/processing/node_generators/select_merge_node.py +161 -64
- trilogy/core/processing/node_generators/select_node.py +13 -13
- trilogy/core/processing/node_generators/union_node.py +12 -11
- trilogy/core/processing/node_generators/unnest_node.py +9 -7
- trilogy/core/processing/node_generators/window_node.py +18 -16
- trilogy/core/processing/nodes/__init__.py +21 -18
- trilogy/core/processing/nodes/base_node.py +82 -66
- trilogy/core/processing/nodes/filter_node.py +19 -13
- trilogy/core/processing/nodes/group_node.py +50 -35
- trilogy/core/processing/nodes/merge_node.py +45 -36
- trilogy/core/processing/nodes/select_node_v2.py +53 -39
- trilogy/core/processing/nodes/union_node.py +5 -7
- trilogy/core/processing/nodes/unnest_node.py +7 -11
- trilogy/core/processing/nodes/window_node.py +9 -4
- trilogy/core/processing/utility.py +103 -75
- trilogy/core/query_processor.py +70 -47
- trilogy/core/statements/__init__.py +0 -0
- trilogy/core/statements/author.py +413 -0
- trilogy/core/statements/build.py +0 -0
- trilogy/core/statements/common.py +30 -0
- trilogy/core/statements/execute.py +42 -0
- trilogy/dialect/base.py +148 -106
- trilogy/dialect/common.py +9 -10
- trilogy/dialect/duckdb.py +1 -1
- trilogy/dialect/enums.py +4 -2
- trilogy/dialect/presto.py +1 -1
- trilogy/dialect/sql_server.py +1 -1
- trilogy/executor.py +44 -32
- trilogy/hooks/__init__.py +4 -0
- trilogy/hooks/base_hook.py +6 -4
- trilogy/hooks/query_debugger.py +113 -97
- trilogy/parser.py +1 -1
- trilogy/parsing/common.py +307 -64
- trilogy/parsing/parse_engine.py +277 -618
- trilogy/parsing/render.py +50 -26
- trilogy/scripts/trilogy.py +2 -1
- pytrilogy-0.0.2.58.dist-info/RECORD +0 -87
- trilogy/core/models.py +0 -4960
- {pytrilogy-0.0.2.58.dist-info → pytrilogy-0.0.3.1.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.58.dist-info → pytrilogy-0.0.3.1.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.2.58.dist-info → pytrilogy-0.0.3.1.dist-info}/top_level.txt +0 -0
|
@@ -2,23 +2,23 @@ from dataclasses import dataclass
|
|
|
2
2
|
from typing import List, Optional
|
|
3
3
|
|
|
4
4
|
from trilogy.constants import logger
|
|
5
|
-
from trilogy.core.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
SourceType,
|
|
5
|
+
from trilogy.core.enums import SourceType
|
|
6
|
+
from trilogy.core.models.build import (
|
|
7
|
+
BuildComparison,
|
|
8
|
+
BuildConcept,
|
|
9
|
+
BuildConditional,
|
|
10
|
+
BuildDatasource,
|
|
11
|
+
BuildGrain,
|
|
12
|
+
BuildOrderBy,
|
|
13
|
+
BuildParenthetical,
|
|
15
14
|
)
|
|
15
|
+
from trilogy.core.models.build_environment import BuildEnvironment
|
|
16
|
+
from trilogy.core.models.execute import QueryDatasource
|
|
16
17
|
from trilogy.core.processing.nodes.base_node import (
|
|
17
18
|
StrategyNode,
|
|
18
19
|
resolve_concept_map,
|
|
19
20
|
)
|
|
20
21
|
from trilogy.core.processing.utility import find_nullable_concepts, is_scalar_condition
|
|
21
|
-
from trilogy.parsing.common import concepts_to_grain_concepts
|
|
22
22
|
from trilogy.utility import unique
|
|
23
23
|
|
|
24
24
|
LOGGER_PREFIX = "[CONCEPT DETAIL - GROUP NODE]"
|
|
@@ -26,8 +26,8 @@ LOGGER_PREFIX = "[CONCEPT DETAIL - GROUP NODE]"
|
|
|
26
26
|
|
|
27
27
|
@dataclass
|
|
28
28
|
class GroupRequiredResponse:
|
|
29
|
-
target:
|
|
30
|
-
upstream:
|
|
29
|
+
target: BuildGrain
|
|
30
|
+
upstream: BuildGrain
|
|
31
31
|
required: bool
|
|
32
32
|
|
|
33
33
|
|
|
@@ -36,19 +36,24 @@ class GroupNode(StrategyNode):
|
|
|
36
36
|
|
|
37
37
|
def __init__(
|
|
38
38
|
self,
|
|
39
|
-
output_concepts: List[
|
|
40
|
-
input_concepts: List[
|
|
41
|
-
environment:
|
|
39
|
+
output_concepts: List[BuildConcept],
|
|
40
|
+
input_concepts: List[BuildConcept],
|
|
41
|
+
environment: BuildEnvironment,
|
|
42
42
|
whole_grain: bool = False,
|
|
43
43
|
parents: List["StrategyNode"] | None = None,
|
|
44
44
|
depth: int = 0,
|
|
45
|
-
partial_concepts: Optional[List[
|
|
46
|
-
nullable_concepts: Optional[List[
|
|
45
|
+
partial_concepts: Optional[List[BuildConcept]] = None,
|
|
46
|
+
nullable_concepts: Optional[List[BuildConcept]] = None,
|
|
47
47
|
force_group: bool | None = None,
|
|
48
|
-
conditions:
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
conditions: (
|
|
49
|
+
BuildConditional | BuildComparison | BuildParenthetical | None
|
|
50
|
+
) = None,
|
|
51
|
+
preexisting_conditions: (
|
|
52
|
+
BuildConditional | BuildComparison | BuildParenthetical | None
|
|
53
|
+
) = None,
|
|
54
|
+
existence_concepts: List[BuildConcept] | None = None,
|
|
51
55
|
hidden_concepts: set[str] | None = None,
|
|
56
|
+
ordering: BuildOrderBy | None = None,
|
|
52
57
|
):
|
|
53
58
|
super().__init__(
|
|
54
59
|
input_concepts=input_concepts,
|
|
@@ -64,27 +69,33 @@ class GroupNode(StrategyNode):
|
|
|
64
69
|
existence_concepts=existence_concepts,
|
|
65
70
|
preexisting_conditions=preexisting_conditions,
|
|
66
71
|
hidden_concepts=hidden_concepts,
|
|
72
|
+
ordering=ordering,
|
|
67
73
|
)
|
|
68
74
|
|
|
69
75
|
@classmethod
|
|
70
76
|
def check_if_required(
|
|
71
77
|
cls,
|
|
72
|
-
downstream_concepts: List[
|
|
73
|
-
parents: list[QueryDatasource |
|
|
74
|
-
environment:
|
|
78
|
+
downstream_concepts: List[BuildConcept],
|
|
79
|
+
parents: list[QueryDatasource | BuildDatasource],
|
|
80
|
+
environment: BuildEnvironment,
|
|
75
81
|
) -> GroupRequiredResponse:
|
|
76
|
-
target_grain =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
environment=environment,
|
|
80
|
-
)
|
|
82
|
+
target_grain = BuildGrain.from_concepts(
|
|
83
|
+
downstream_concepts,
|
|
84
|
+
environment=environment,
|
|
81
85
|
)
|
|
82
|
-
|
|
86
|
+
|
|
87
|
+
# the concepts of the souce grain might not exist in the output environment
|
|
88
|
+
# so we need to construct a new
|
|
89
|
+
concept_map: dict[str, BuildConcept] = {}
|
|
90
|
+
comp_grain = BuildGrain()
|
|
83
91
|
for source in parents:
|
|
84
92
|
comp_grain += source.grain
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
for x in source.output_concepts:
|
|
94
|
+
concept_map[x.address] = x
|
|
95
|
+
lookups = [
|
|
96
|
+
concept_map[x] if x in concept_map else x for x in comp_grain.components
|
|
97
|
+
]
|
|
98
|
+
comp_grain = BuildGrain.from_concepts(lookups, environment=environment)
|
|
88
99
|
# dynamically select if we need to group
|
|
89
100
|
# because sometimes, we are already at required grain
|
|
90
101
|
if comp_grain.issubset(target_grain):
|
|
@@ -93,7 +104,7 @@ class GroupNode(StrategyNode):
|
|
|
93
104
|
return GroupRequiredResponse(target_grain, comp_grain, True)
|
|
94
105
|
|
|
95
106
|
def _resolve(self) -> QueryDatasource:
|
|
96
|
-
parent_sources: List[QueryDatasource |
|
|
107
|
+
parent_sources: List[QueryDatasource | BuildDatasource] = [
|
|
97
108
|
p.resolve() for p in self.parents
|
|
98
109
|
]
|
|
99
110
|
grains = self.check_if_required(
|
|
@@ -145,13 +156,15 @@ class GroupNode(StrategyNode):
|
|
|
145
156
|
nullable_concepts=nullable_concepts,
|
|
146
157
|
hidden_concepts=self.hidden_concepts,
|
|
147
158
|
condition=self.conditions,
|
|
159
|
+
ordering=self.ordering,
|
|
148
160
|
)
|
|
149
161
|
# if there is a condition on a group node and it's not scalar
|
|
150
162
|
# inject an additional CTE
|
|
151
163
|
if self.conditions and not is_scalar_condition(self.conditions):
|
|
152
164
|
base.condition = None
|
|
153
165
|
base.output_concepts = unique(
|
|
154
|
-
base.output_concepts + self.conditions.row_arguments,
|
|
166
|
+
list(base.output_concepts) + list(self.conditions.row_arguments),
|
|
167
|
+
"address",
|
|
155
168
|
)
|
|
156
169
|
# re-visible any hidden concepts
|
|
157
170
|
base.hidden_concepts = set(
|
|
@@ -174,6 +187,7 @@ class GroupNode(StrategyNode):
|
|
|
174
187
|
partial_concepts=self.partial_concepts,
|
|
175
188
|
condition=self.conditions,
|
|
176
189
|
hidden_concepts=self.hidden_concepts,
|
|
190
|
+
ordering=self.ordering,
|
|
177
191
|
)
|
|
178
192
|
return base
|
|
179
193
|
|
|
@@ -192,4 +206,5 @@ class GroupNode(StrategyNode):
|
|
|
192
206
|
preexisting_conditions=self.preexisting_conditions,
|
|
193
207
|
existence_concepts=list(self.existence_concepts),
|
|
194
208
|
hidden_concepts=set(self.hidden_concepts),
|
|
209
|
+
ordering=self.ordering,
|
|
195
210
|
)
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
from typing import List, Optional, Tuple
|
|
2
2
|
|
|
3
3
|
from trilogy.constants import logger
|
|
4
|
-
from trilogy.core.
|
|
5
|
-
BaseJoin,
|
|
6
|
-
Comparison,
|
|
7
|
-
Concept,
|
|
8
|
-
Conditional,
|
|
9
|
-
Datasource,
|
|
10
|
-
Environment,
|
|
11
|
-
Grain,
|
|
4
|
+
from trilogy.core.enums import (
|
|
12
5
|
JoinType,
|
|
13
|
-
Parenthetical,
|
|
14
|
-
QueryDatasource,
|
|
15
6
|
SourceType,
|
|
16
|
-
UnnestJoin,
|
|
17
7
|
)
|
|
8
|
+
from trilogy.core.models.build import (
|
|
9
|
+
BuildComparison,
|
|
10
|
+
BuildConcept,
|
|
11
|
+
BuildConditional,
|
|
12
|
+
BuildDatasource,
|
|
13
|
+
BuildGrain,
|
|
14
|
+
BuildOrderBy,
|
|
15
|
+
BuildParenthetical,
|
|
16
|
+
)
|
|
17
|
+
from trilogy.core.models.build_environment import BuildEnvironment
|
|
18
|
+
from trilogy.core.models.execute import BaseJoin, QueryDatasource, UnnestJoin
|
|
18
19
|
from trilogy.core.processing.nodes.base_node import (
|
|
19
20
|
NodeJoin,
|
|
20
21
|
StrategyNode,
|
|
@@ -27,10 +28,10 @@ LOGGER_PREFIX = "[CONCEPT DETAIL - MERGE NODE]"
|
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
def deduplicate_nodes(
|
|
30
|
-
merged: dict[str, QueryDatasource |
|
|
31
|
+
merged: dict[str, QueryDatasource | BuildDatasource],
|
|
31
32
|
logging_prefix: str,
|
|
32
|
-
environment:
|
|
33
|
-
) -> tuple[bool, dict[str, QueryDatasource |
|
|
33
|
+
environment: BuildEnvironment,
|
|
34
|
+
) -> tuple[bool, dict[str, QueryDatasource | BuildDatasource], set[str]]:
|
|
34
35
|
duplicates = False
|
|
35
36
|
removed: set[str] = set()
|
|
36
37
|
set_map: dict[str, set[str]] = {}
|
|
@@ -73,10 +74,10 @@ def deduplicate_nodes(
|
|
|
73
74
|
|
|
74
75
|
def deduplicate_nodes_and_joins(
|
|
75
76
|
joins: List[NodeJoin] | None,
|
|
76
|
-
merged: dict[str, QueryDatasource |
|
|
77
|
+
merged: dict[str, QueryDatasource | BuildDatasource],
|
|
77
78
|
logging_prefix: str,
|
|
78
|
-
environment:
|
|
79
|
-
) -> Tuple[List[NodeJoin] | None, dict[str, QueryDatasource |
|
|
79
|
+
environment: BuildEnvironment,
|
|
80
|
+
) -> Tuple[List[NodeJoin] | None, dict[str, QueryDatasource | BuildDatasource]]:
|
|
80
81
|
# it's possible that we have more sources than we need
|
|
81
82
|
duplicates = True
|
|
82
83
|
while duplicates:
|
|
@@ -100,24 +101,29 @@ class MergeNode(StrategyNode):
|
|
|
100
101
|
|
|
101
102
|
def __init__(
|
|
102
103
|
self,
|
|
103
|
-
input_concepts: List[
|
|
104
|
-
output_concepts: List[
|
|
104
|
+
input_concepts: List[BuildConcept],
|
|
105
|
+
output_concepts: List[BuildConcept],
|
|
105
106
|
environment,
|
|
106
107
|
whole_grain: bool = False,
|
|
107
108
|
parents: List["StrategyNode"] | None = None,
|
|
108
109
|
node_joins: List[NodeJoin] | None = None,
|
|
109
110
|
join_concepts: Optional[List] = None,
|
|
110
111
|
force_join_type: Optional[JoinType] = None,
|
|
111
|
-
partial_concepts: Optional[List[
|
|
112
|
-
nullable_concepts: Optional[List[
|
|
112
|
+
partial_concepts: Optional[List[BuildConcept]] = None,
|
|
113
|
+
nullable_concepts: Optional[List[BuildConcept]] = None,
|
|
113
114
|
force_group: bool | None = None,
|
|
114
115
|
depth: int = 0,
|
|
115
|
-
grain:
|
|
116
|
-
conditions:
|
|
117
|
-
|
|
116
|
+
grain: BuildGrain | None = None,
|
|
117
|
+
conditions: (
|
|
118
|
+
BuildConditional | BuildComparison | BuildParenthetical | None
|
|
119
|
+
) = None,
|
|
120
|
+
preexisting_conditions: (
|
|
121
|
+
BuildConditional | BuildComparison | BuildParenthetical | None
|
|
122
|
+
) = None,
|
|
118
123
|
hidden_concepts: set[str] | None = None,
|
|
119
|
-
virtual_output_concepts: List[
|
|
120
|
-
existence_concepts: List[
|
|
124
|
+
virtual_output_concepts: List[BuildConcept] | None = None,
|
|
125
|
+
existence_concepts: List[BuildConcept] | None = None,
|
|
126
|
+
ordering: BuildOrderBy | None = None,
|
|
121
127
|
):
|
|
122
128
|
super().__init__(
|
|
123
129
|
input_concepts=input_concepts,
|
|
@@ -135,6 +141,7 @@ class MergeNode(StrategyNode):
|
|
|
135
141
|
hidden_concepts=hidden_concepts,
|
|
136
142
|
virtual_output_concepts=virtual_output_concepts,
|
|
137
143
|
existence_concepts=existence_concepts,
|
|
144
|
+
ordering=ordering,
|
|
138
145
|
)
|
|
139
146
|
self.join_concepts = join_concepts
|
|
140
147
|
self.force_join_type = force_join_type
|
|
@@ -166,7 +173,7 @@ class MergeNode(StrategyNode):
|
|
|
166
173
|
)
|
|
167
174
|
return joins
|
|
168
175
|
|
|
169
|
-
def create_full_joins(self, dataset_list: List[QueryDatasource |
|
|
176
|
+
def create_full_joins(self, dataset_list: List[QueryDatasource | BuildDatasource]):
|
|
170
177
|
joins = []
|
|
171
178
|
seen = set()
|
|
172
179
|
for left_value in dataset_list:
|
|
@@ -191,12 +198,12 @@ class MergeNode(StrategyNode):
|
|
|
191
198
|
self,
|
|
192
199
|
final_datasets,
|
|
193
200
|
final_joins: List[NodeJoin] | None,
|
|
194
|
-
pregrain:
|
|
195
|
-
grain:
|
|
196
|
-
environment:
|
|
201
|
+
pregrain: BuildGrain,
|
|
202
|
+
grain: BuildGrain,
|
|
203
|
+
environment: BuildEnvironment,
|
|
197
204
|
) -> List[BaseJoin | UnnestJoin]:
|
|
198
205
|
# only finally, join between them for unique values
|
|
199
|
-
dataset_list: List[QueryDatasource |
|
|
206
|
+
dataset_list: List[QueryDatasource | BuildDatasource] = sorted(
|
|
200
207
|
final_datasets, key=lambda x: -len(x.grain.components)
|
|
201
208
|
)
|
|
202
209
|
|
|
@@ -227,10 +234,10 @@ class MergeNode(StrategyNode):
|
|
|
227
234
|
return joins
|
|
228
235
|
|
|
229
236
|
def _resolve(self) -> QueryDatasource:
|
|
230
|
-
parent_sources: List[QueryDatasource |
|
|
237
|
+
parent_sources: List[QueryDatasource | BuildDatasource] = [
|
|
231
238
|
p.resolve() for p in self.parents
|
|
232
239
|
]
|
|
233
|
-
merged: dict[str, QueryDatasource |
|
|
240
|
+
merged: dict[str, QueryDatasource | BuildDatasource] = {}
|
|
234
241
|
final_joins: List[NodeJoin] | None = self.node_joins
|
|
235
242
|
for source in parent_sources:
|
|
236
243
|
if source.identifier in merged:
|
|
@@ -246,7 +253,7 @@ class MergeNode(StrategyNode):
|
|
|
246
253
|
final_joins, merged, self.logging_prefix, self.environment
|
|
247
254
|
)
|
|
248
255
|
# early exit if we can just return the parent
|
|
249
|
-
final_datasets: List[QueryDatasource |
|
|
256
|
+
final_datasets: List[QueryDatasource | BuildDatasource] = list(merged.values())
|
|
250
257
|
|
|
251
258
|
existence_final = [
|
|
252
259
|
x
|
|
@@ -254,7 +261,7 @@ class MergeNode(StrategyNode):
|
|
|
254
261
|
if all([y in self.existence_concepts for y in x.output_concepts])
|
|
255
262
|
]
|
|
256
263
|
if len(merged.keys()) == 1:
|
|
257
|
-
final: QueryDatasource |
|
|
264
|
+
final: QueryDatasource | BuildDatasource = list(merged.values())[0]
|
|
258
265
|
if (
|
|
259
266
|
set([c.address for c in final.output_concepts])
|
|
260
267
|
== set([c.address for c in self.output_concepts])
|
|
@@ -288,7 +295,7 @@ class MergeNode(StrategyNode):
|
|
|
288
295
|
)
|
|
289
296
|
return dataset
|
|
290
297
|
|
|
291
|
-
pregrain =
|
|
298
|
+
pregrain = BuildGrain()
|
|
292
299
|
for source in final_datasets:
|
|
293
300
|
pregrain += source.grain
|
|
294
301
|
|
|
@@ -352,6 +359,7 @@ class MergeNode(StrategyNode):
|
|
|
352
359
|
force_group=force_group,
|
|
353
360
|
condition=self.conditions,
|
|
354
361
|
hidden_concepts=self.hidden_concepts,
|
|
362
|
+
ordering=self.ordering,
|
|
355
363
|
)
|
|
356
364
|
return qds
|
|
357
365
|
|
|
@@ -375,4 +383,5 @@ class MergeNode(StrategyNode):
|
|
|
375
383
|
join_concepts=list(self.join_concepts) if self.join_concepts else None,
|
|
376
384
|
force_join_type=self.force_join_type,
|
|
377
385
|
existence_concepts=list(self.existence_concepts),
|
|
386
|
+
ordering=self.ordering,
|
|
378
387
|
)
|
|
@@ -2,20 +2,19 @@ from typing import List, Optional
|
|
|
2
2
|
|
|
3
3
|
from trilogy.constants import logger
|
|
4
4
|
from trilogy.core.constants import CONSTANT_DATASET
|
|
5
|
-
from trilogy.core.enums import Purpose,
|
|
6
|
-
from trilogy.core.models import (
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
QueryDatasource,
|
|
16
|
-
SourceType,
|
|
17
|
-
UnnestJoin,
|
|
5
|
+
from trilogy.core.enums import Derivation, Purpose, SourceType
|
|
6
|
+
from trilogy.core.models.build import (
|
|
7
|
+
BuildComparison,
|
|
8
|
+
BuildConcept,
|
|
9
|
+
BuildConditional,
|
|
10
|
+
BuildDatasource,
|
|
11
|
+
BuildFunction,
|
|
12
|
+
BuildGrain,
|
|
13
|
+
BuildOrderBy,
|
|
14
|
+
BuildParenthetical,
|
|
18
15
|
)
|
|
16
|
+
from trilogy.core.models.build_environment import BuildEnvironment
|
|
17
|
+
from trilogy.core.models.execute import QueryDatasource, UnnestJoin
|
|
19
18
|
from trilogy.core.processing.nodes.base_node import StrategyNode, resolve_concept_map
|
|
20
19
|
from trilogy.utility import unique
|
|
21
20
|
|
|
@@ -31,21 +30,26 @@ class SelectNode(StrategyNode):
|
|
|
31
30
|
|
|
32
31
|
def __init__(
|
|
33
32
|
self,
|
|
34
|
-
input_concepts: List[
|
|
35
|
-
output_concepts: List[
|
|
36
|
-
environment:
|
|
37
|
-
datasource:
|
|
33
|
+
input_concepts: List[BuildConcept],
|
|
34
|
+
output_concepts: List[BuildConcept],
|
|
35
|
+
environment: BuildEnvironment,
|
|
36
|
+
datasource: BuildDatasource | None = None,
|
|
38
37
|
whole_grain: bool = False,
|
|
39
38
|
parents: List["StrategyNode"] | None = None,
|
|
40
39
|
depth: int = 0,
|
|
41
|
-
partial_concepts: List[
|
|
42
|
-
nullable_concepts: List[
|
|
40
|
+
partial_concepts: List[BuildConcept] | None = None,
|
|
41
|
+
nullable_concepts: List[BuildConcept] | None = None,
|
|
43
42
|
accept_partial: bool = False,
|
|
44
|
-
grain: Optional[
|
|
43
|
+
grain: Optional[BuildGrain] = None,
|
|
45
44
|
force_group: bool | None = False,
|
|
46
|
-
conditions:
|
|
47
|
-
|
|
45
|
+
conditions: (
|
|
46
|
+
BuildConditional | BuildComparison | BuildParenthetical | None
|
|
47
|
+
) = None,
|
|
48
|
+
preexisting_conditions: (
|
|
49
|
+
BuildConditional | BuildComparison | BuildParenthetical | None
|
|
50
|
+
) = None,
|
|
48
51
|
hidden_concepts: set[str] | None = None,
|
|
52
|
+
ordering: BuildOrderBy | None = None,
|
|
49
53
|
):
|
|
50
54
|
super().__init__(
|
|
51
55
|
input_concepts=input_concepts,
|
|
@@ -61,6 +65,7 @@ class SelectNode(StrategyNode):
|
|
|
61
65
|
conditions=conditions,
|
|
62
66
|
preexisting_conditions=preexisting_conditions,
|
|
63
67
|
hidden_concepts=hidden_concepts,
|
|
68
|
+
ordering=ordering,
|
|
64
69
|
)
|
|
65
70
|
self.accept_partial = accept_partial
|
|
66
71
|
self.datasource = datasource
|
|
@@ -75,31 +80,31 @@ class SelectNode(StrategyNode):
|
|
|
75
80
|
) -> QueryDatasource:
|
|
76
81
|
if not self.datasource:
|
|
77
82
|
raise ValueError("Datasource not provided")
|
|
78
|
-
datasource:
|
|
83
|
+
datasource: BuildDatasource = self.datasource
|
|
79
84
|
|
|
80
|
-
all_concepts_final: List[
|
|
81
|
-
source_map: dict[str, set[
|
|
85
|
+
all_concepts_final: List[BuildConcept] = unique(self.all_concepts, "address")
|
|
86
|
+
source_map: dict[str, set[BuildDatasource | QueryDatasource | UnnestJoin]] = {
|
|
82
87
|
concept.address: {datasource} for concept in self.input_concepts
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
derived_concepts = [
|
|
86
91
|
c
|
|
87
92
|
for c in datasource.columns
|
|
88
|
-
if isinstance(c.alias,
|
|
93
|
+
if isinstance(c.alias, BuildFunction) and c.concept.address in source_map
|
|
89
94
|
]
|
|
90
95
|
for c in derived_concepts:
|
|
91
|
-
if not isinstance(c.alias,
|
|
96
|
+
if not isinstance(c.alias, BuildFunction):
|
|
92
97
|
continue
|
|
93
98
|
for x in c.alias.concept_arguments:
|
|
94
99
|
source_map[x.address] = {datasource}
|
|
95
100
|
for x in all_concepts_final:
|
|
96
101
|
if x.address not in source_map and x.derivation in (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
Derivation.MULTISELECT,
|
|
103
|
+
Derivation.FILTER,
|
|
104
|
+
Derivation.BASIC,
|
|
105
|
+
Derivation.ROWSET,
|
|
106
|
+
Derivation.BASIC,
|
|
107
|
+
Derivation.UNION,
|
|
103
108
|
):
|
|
104
109
|
source_map[x.address] = set()
|
|
105
110
|
|
|
@@ -109,7 +114,7 @@ class SelectNode(StrategyNode):
|
|
|
109
114
|
if self.force_group is False:
|
|
110
115
|
grain = datasource.grain
|
|
111
116
|
else:
|
|
112
|
-
grain = self.grain or
|
|
117
|
+
grain = self.grain or BuildGrain()
|
|
113
118
|
return QueryDatasource(
|
|
114
119
|
input_concepts=self.input_concepts,
|
|
115
120
|
output_concepts=all_concepts_final,
|
|
@@ -127,10 +132,11 @@ class SelectNode(StrategyNode):
|
|
|
127
132
|
# select nodes should never group
|
|
128
133
|
force_group=self.force_group,
|
|
129
134
|
hidden_concepts=self.hidden_concepts,
|
|
135
|
+
ordering=self.ordering,
|
|
130
136
|
)
|
|
131
137
|
|
|
132
138
|
def resolve_from_constant_datasources(self) -> QueryDatasource:
|
|
133
|
-
datasource =
|
|
139
|
+
datasource = BuildDatasource(
|
|
134
140
|
name=CONSTANT_DATASET, address=CONSTANT_DATASET, columns=[]
|
|
135
141
|
)
|
|
136
142
|
return QueryDatasource(
|
|
@@ -144,6 +150,7 @@ class SelectNode(StrategyNode):
|
|
|
144
150
|
partial_concepts=[],
|
|
145
151
|
source_type=SourceType.CONSTANT,
|
|
146
152
|
hidden_concepts=self.hidden_concepts,
|
|
153
|
+
ordering=self.ordering,
|
|
147
154
|
)
|
|
148
155
|
|
|
149
156
|
def _resolve(self) -> QueryDatasource:
|
|
@@ -152,10 +159,10 @@ class SelectNode(StrategyNode):
|
|
|
152
159
|
if all(
|
|
153
160
|
[
|
|
154
161
|
(
|
|
155
|
-
c.derivation ==
|
|
162
|
+
c.derivation == Derivation.CONSTANT
|
|
156
163
|
or (
|
|
157
164
|
c.purpose == Purpose.CONSTANT
|
|
158
|
-
and c.derivation ==
|
|
165
|
+
and c.derivation == Derivation.MULTISELECT
|
|
159
166
|
)
|
|
160
167
|
)
|
|
161
168
|
for c in self.all_concepts
|
|
@@ -165,6 +172,8 @@ class SelectNode(StrategyNode):
|
|
|
165
172
|
f"{self.logging_prefix}{LOGGER_PREFIX} have a constant datasource"
|
|
166
173
|
)
|
|
167
174
|
resolution = self.resolve_from_constant_datasources()
|
|
175
|
+
return resolution
|
|
176
|
+
|
|
168
177
|
if self.datasource and not resolution:
|
|
169
178
|
resolution = self.resolve_from_provided_datasource()
|
|
170
179
|
|
|
@@ -172,7 +181,7 @@ class SelectNode(StrategyNode):
|
|
|
172
181
|
if not resolution:
|
|
173
182
|
return super()._resolve()
|
|
174
183
|
# zip in our parent source map
|
|
175
|
-
parent_sources: List[QueryDatasource |
|
|
184
|
+
parent_sources: List[QueryDatasource | BuildDatasource] = [
|
|
176
185
|
p.resolve() for p in self.parents
|
|
177
186
|
]
|
|
178
187
|
|
|
@@ -187,7 +196,7 @@ class SelectNode(StrategyNode):
|
|
|
187
196
|
if v and k not in resolution.source_map:
|
|
188
197
|
resolution.source_map[k] = v
|
|
189
198
|
if not resolution:
|
|
190
|
-
raise ValueError("No select node could be generated")
|
|
199
|
+
raise ValueError(f"No select node could be generated for {self}")
|
|
191
200
|
return resolution
|
|
192
201
|
|
|
193
202
|
def copy(self) -> "SelectNode":
|
|
@@ -207,6 +216,7 @@ class SelectNode(StrategyNode):
|
|
|
207
216
|
conditions=self.conditions,
|
|
208
217
|
preexisting_conditions=self.preexisting_conditions,
|
|
209
218
|
hidden_concepts=self.hidden_concepts,
|
|
219
|
+
ordering=self.ordering,
|
|
210
220
|
)
|
|
211
221
|
|
|
212
222
|
|
|
@@ -224,4 +234,8 @@ class ConstantNode(SelectNode):
|
|
|
224
234
|
conditions=self.conditions,
|
|
225
235
|
preexisting_conditions=self.preexisting_conditions,
|
|
226
236
|
hidden_concepts=self.hidden_concepts,
|
|
237
|
+
ordering=self.ordering,
|
|
227
238
|
)
|
|
239
|
+
|
|
240
|
+
def _resolve(self) -> QueryDatasource:
|
|
241
|
+
return self.resolve_from_constant_datasources()
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
-
from trilogy.core.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
SourceType,
|
|
7
|
-
)
|
|
3
|
+
from trilogy.core.enums import SourceType
|
|
4
|
+
from trilogy.core.models.build import BuildConcept
|
|
5
|
+
from trilogy.core.models.execute import QueryDatasource
|
|
8
6
|
from trilogy.core.processing.nodes.base_node import StrategyNode
|
|
9
7
|
|
|
10
8
|
|
|
@@ -15,8 +13,8 @@ class UnionNode(StrategyNode):
|
|
|
15
13
|
|
|
16
14
|
def __init__(
|
|
17
15
|
self,
|
|
18
|
-
input_concepts: List[
|
|
19
|
-
output_concepts: List[
|
|
16
|
+
input_concepts: List[BuildConcept],
|
|
17
|
+
output_concepts: List[BuildConcept],
|
|
20
18
|
environment,
|
|
21
19
|
whole_grain: bool = False,
|
|
22
20
|
parents: List["StrategyNode"] | None = None,
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
-
from trilogy.core.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
QueryDatasource,
|
|
7
|
-
SourceType,
|
|
8
|
-
UnnestJoin,
|
|
9
|
-
)
|
|
3
|
+
from trilogy.core.enums import SourceType
|
|
4
|
+
from trilogy.core.models.build import BuildConcept, BuildFunction
|
|
5
|
+
from trilogy.core.models.execute import QueryDatasource, UnnestJoin
|
|
10
6
|
from trilogy.core.processing.nodes.base_node import StrategyNode
|
|
11
7
|
|
|
12
8
|
|
|
@@ -19,9 +15,9 @@ class UnnestNode(StrategyNode):
|
|
|
19
15
|
|
|
20
16
|
def __init__(
|
|
21
17
|
self,
|
|
22
|
-
unnest_concepts: List[
|
|
23
|
-
input_concepts: List[
|
|
24
|
-
output_concepts: List[
|
|
18
|
+
unnest_concepts: List[BuildConcept],
|
|
19
|
+
input_concepts: List[BuildConcept],
|
|
20
|
+
output_concepts: List[BuildConcept],
|
|
25
21
|
environment,
|
|
26
22
|
whole_grain: bool = False,
|
|
27
23
|
parents: List["StrategyNode"] | None = None,
|
|
@@ -41,7 +37,7 @@ class UnnestNode(StrategyNode):
|
|
|
41
37
|
"""We need to ensure that any filtered values are removed from the output to avoid inappropriate references"""
|
|
42
38
|
base = super()._resolve()
|
|
43
39
|
lineage = self.unnest_concepts[0].lineage
|
|
44
|
-
assert isinstance(lineage,
|
|
40
|
+
assert isinstance(lineage, BuildFunction)
|
|
45
41
|
final = "_".join(set([c.address for c in self.unnest_concepts]))
|
|
46
42
|
unnest = UnnestJoin(
|
|
47
43
|
concepts=self.unnest_concepts,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
-
from trilogy.core.
|
|
4
|
-
from trilogy.core.
|
|
3
|
+
from trilogy.core.enums import SourceType
|
|
4
|
+
from trilogy.core.models.build import BuildConcept, BuildOrderBy
|
|
5
|
+
from trilogy.core.models.execute import QueryDatasource
|
|
6
|
+
from trilogy.core.processing.nodes.base_node import StrategyNode
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
class WindowNode(StrategyNode):
|
|
@@ -9,12 +11,13 @@ class WindowNode(StrategyNode):
|
|
|
9
11
|
|
|
10
12
|
def __init__(
|
|
11
13
|
self,
|
|
12
|
-
input_concepts: List[
|
|
13
|
-
output_concepts: List[
|
|
14
|
+
input_concepts: List[BuildConcept],
|
|
15
|
+
output_concepts: List[BuildConcept],
|
|
14
16
|
environment,
|
|
15
17
|
whole_grain: bool = False,
|
|
16
18
|
parents: List["StrategyNode"] | None = None,
|
|
17
19
|
depth: int = 0,
|
|
20
|
+
ordering: BuildOrderBy | None = None,
|
|
18
21
|
):
|
|
19
22
|
super().__init__(
|
|
20
23
|
input_concepts=input_concepts,
|
|
@@ -23,6 +26,7 @@ class WindowNode(StrategyNode):
|
|
|
23
26
|
whole_grain=whole_grain,
|
|
24
27
|
parents=parents,
|
|
25
28
|
depth=depth,
|
|
29
|
+
ordering=ordering,
|
|
26
30
|
)
|
|
27
31
|
|
|
28
32
|
def _resolve(self) -> QueryDatasource:
|
|
@@ -37,4 +41,5 @@ class WindowNode(StrategyNode):
|
|
|
37
41
|
whole_grain=self.whole_grain,
|
|
38
42
|
parents=self.parents,
|
|
39
43
|
depth=self.depth,
|
|
44
|
+
ordering=self.ordering,
|
|
40
45
|
)
|