pytrilogy 0.0.2.57__py3-none-any.whl → 0.0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pytrilogy-0.0.2.57.dist-info → pytrilogy-0.0.3.0.dist-info}/METADATA +9 -2
- pytrilogy-0.0.3.0.dist-info/RECORD +99 -0
- {pytrilogy-0.0.2.57.dist-info → pytrilogy-0.0.3.0.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 +1845 -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 +696 -0
- trilogy/core/models/execute.py +931 -0
- trilogy/core/optimization.py +17 -22
- 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 +181 -146
- 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 +51 -45
- 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 +13 -10
- 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 +59 -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 +19 -16
- trilogy/core/processing/nodes/__init__.py +21 -18
- trilogy/core/processing/nodes/base_node.py +92 -77
- trilogy/core/processing/nodes/filter_node.py +19 -13
- trilogy/core/processing/nodes/group_node.py +55 -40
- trilogy/core/processing/nodes/merge_node.py +47 -38
- trilogy/core/processing/nodes/select_node_v2.py +54 -40
- 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 +108 -80
- trilogy/core/query_processor.py +67 -49
- 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 +152 -111
- 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/base_hook.py +6 -4
- trilogy/hooks/query_debugger.py +110 -93
- trilogy/parser.py +1 -1
- trilogy/parsing/common.py +303 -64
- trilogy/parsing/parse_engine.py +263 -617
- trilogy/parsing/render.py +50 -26
- trilogy/scripts/trilogy.py +2 -1
- pytrilogy-0.0.2.57.dist-info/RECORD +0 -87
- trilogy/core/models.py +0 -4960
- {pytrilogy-0.0.2.57.dist-info → pytrilogy-0.0.3.0.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.57.dist-info → pytrilogy-0.0.3.0.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.2.57.dist-info → pytrilogy-0.0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
# directly select out a basic derivation
|
|
2
1
|
from typing import List
|
|
3
2
|
|
|
4
3
|
from trilogy.constants import logger
|
|
5
|
-
from trilogy.core.enums import SourceType
|
|
6
|
-
from trilogy.core.models import
|
|
7
|
-
|
|
8
|
-
Function,
|
|
9
|
-
FunctionClass,
|
|
10
|
-
WhereClause,
|
|
11
|
-
)
|
|
4
|
+
from trilogy.core.enums import FunctionClass, SourceType
|
|
5
|
+
from trilogy.core.models.build import BuildConcept, BuildFunction, BuildWhereClause
|
|
6
|
+
from trilogy.core.models.build_environment import BuildEnvironment
|
|
12
7
|
from trilogy.core.processing.node_generators.common import (
|
|
13
8
|
resolve_function_parent_concepts,
|
|
14
9
|
)
|
|
@@ -19,10 +14,12 @@ LOGGER_PREFIX = "[GEN_BASIC_NODE]"
|
|
|
19
14
|
|
|
20
15
|
|
|
21
16
|
def is_equivalent_basic_function_lineage(
|
|
22
|
-
x:
|
|
23
|
-
y:
|
|
17
|
+
x: BuildConcept,
|
|
18
|
+
y: BuildConcept,
|
|
24
19
|
):
|
|
25
|
-
if not isinstance(x.lineage,
|
|
20
|
+
if not isinstance(x.lineage, BuildFunction) or not isinstance(
|
|
21
|
+
y.lineage, BuildFunction
|
|
22
|
+
):
|
|
26
23
|
return False
|
|
27
24
|
if x.lineage.operator == y.lineage.operator:
|
|
28
25
|
return True
|
|
@@ -35,20 +32,20 @@ def is_equivalent_basic_function_lineage(
|
|
|
35
32
|
|
|
36
33
|
|
|
37
34
|
def gen_basic_node(
|
|
38
|
-
concept:
|
|
39
|
-
local_optional: List[
|
|
40
|
-
environment,
|
|
35
|
+
concept: BuildConcept,
|
|
36
|
+
local_optional: List[BuildConcept],
|
|
37
|
+
environment: BuildEnvironment,
|
|
41
38
|
g,
|
|
42
39
|
depth: int,
|
|
43
40
|
source_concepts,
|
|
44
41
|
history: History | None = None,
|
|
45
|
-
conditions:
|
|
42
|
+
conditions: BuildWhereClause | None = None,
|
|
46
43
|
):
|
|
47
44
|
depth_prefix = "\t" * depth
|
|
48
45
|
parent_concepts = resolve_function_parent_concepts(concept, environment=environment)
|
|
49
46
|
|
|
50
47
|
logger.info(
|
|
51
|
-
f"{depth_prefix}{LOGGER_PREFIX} basic node for {concept} has parents {[x
|
|
48
|
+
f"{depth_prefix}{LOGGER_PREFIX} basic node for {concept} with lineage {concept.lineage} has parents {[x for x in parent_concepts]}"
|
|
52
49
|
)
|
|
53
50
|
|
|
54
51
|
equivalent_optional = [
|
|
@@ -57,6 +54,7 @@ def gen_basic_node(
|
|
|
57
54
|
if is_equivalent_basic_function_lineage(concept, x)
|
|
58
55
|
and x.address != concept.address
|
|
59
56
|
]
|
|
57
|
+
|
|
60
58
|
if equivalent_optional:
|
|
61
59
|
logger.info(
|
|
62
60
|
f"{depth_prefix}{LOGGER_PREFIX} basic node for {concept} has equivalent optional {[x.address for x in equivalent_optional]}"
|
|
@@ -64,9 +62,12 @@ def gen_basic_node(
|
|
|
64
62
|
for eo in equivalent_optional:
|
|
65
63
|
parent_concepts += resolve_function_parent_concepts(eo, environment=environment)
|
|
66
64
|
non_equivalent_optional = [
|
|
67
|
-
x
|
|
65
|
+
x
|
|
66
|
+
for x in local_optional
|
|
67
|
+
if x not in equivalent_optional
|
|
68
|
+
and not any(x.address in y.pseudonyms for y in equivalent_optional)
|
|
68
69
|
]
|
|
69
|
-
all_parents: list[
|
|
70
|
+
all_parents: list[BuildConcept] = unique(
|
|
70
71
|
parent_concepts + non_equivalent_optional, "address"
|
|
71
72
|
)
|
|
72
73
|
logger.info(
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
2
|
from typing import Callable, List, Tuple
|
|
3
3
|
|
|
4
|
-
from trilogy.core.enums import
|
|
5
|
-
from trilogy.core.models import (
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
WhereClause,
|
|
4
|
+
from trilogy.core.enums import Derivation, Purpose
|
|
5
|
+
from trilogy.core.models.build import (
|
|
6
|
+
BuildAggregateWrapper,
|
|
7
|
+
BuildConcept,
|
|
8
|
+
BuildFilterItem,
|
|
9
|
+
BuildFunction,
|
|
10
|
+
BuildWhereClause,
|
|
11
|
+
LooseBuildConceptList,
|
|
13
12
|
)
|
|
13
|
+
from trilogy.core.models.build_environment import BuildEnvironment
|
|
14
14
|
from trilogy.core.processing.nodes import (
|
|
15
15
|
History,
|
|
16
16
|
NodeJoin,
|
|
@@ -19,14 +19,19 @@ from trilogy.core.processing.nodes.base_node import StrategyNode
|
|
|
19
19
|
from trilogy.core.processing.nodes.merge_node import MergeNode
|
|
20
20
|
from trilogy.utility import unique
|
|
21
21
|
|
|
22
|
+
AGGREGATE_TYPES = (BuildAggregateWrapper,)
|
|
23
|
+
FUNCTION_TYPES = (BuildFunction,)
|
|
24
|
+
|
|
22
25
|
|
|
23
26
|
def resolve_function_parent_concepts(
|
|
24
|
-
concept:
|
|
25
|
-
) -> List[
|
|
26
|
-
if not isinstance(concept.lineage, (
|
|
27
|
-
raise ValueError(
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
concept: BuildConcept, environment: BuildEnvironment
|
|
28
|
+
) -> List[BuildConcept]:
|
|
29
|
+
if not isinstance(concept.lineage, (*FUNCTION_TYPES, *AGGREGATE_TYPES)):
|
|
30
|
+
raise ValueError(
|
|
31
|
+
f"Concept {concept} lineage is not function or aggregate, is {type(concept.lineage)}"
|
|
32
|
+
)
|
|
33
|
+
if concept.derivation == Derivation.AGGREGATE:
|
|
34
|
+
base: list[BuildConcept] = []
|
|
30
35
|
if not concept.grain.abstract:
|
|
31
36
|
base = concept.lineage.concept_arguments + [
|
|
32
37
|
environment.concepts[c] for c in concept.grain.components
|
|
@@ -35,13 +40,13 @@ def resolve_function_parent_concepts(
|
|
|
35
40
|
# keep the key as a parent
|
|
36
41
|
else:
|
|
37
42
|
base = concept.lineage.concept_arguments
|
|
38
|
-
if isinstance(concept.lineage,
|
|
43
|
+
if isinstance(concept.lineage, AGGREGATE_TYPES):
|
|
39
44
|
# for aggregate wrapper, don't include the by
|
|
40
45
|
extra_property_grain = concept.lineage.function.concept_arguments
|
|
41
46
|
else:
|
|
42
47
|
extra_property_grain = concept.lineage.concept_arguments
|
|
43
48
|
for x in extra_property_grain:
|
|
44
|
-
if isinstance(x,
|
|
49
|
+
if isinstance(x, BuildConcept) and x.purpose == Purpose.PROPERTY and x.keys:
|
|
45
50
|
base += [environment.concepts[c] for c in x.keys]
|
|
46
51
|
return unique(base, "address")
|
|
47
52
|
# TODO: handle basic lineage chains?
|
|
@@ -49,10 +54,10 @@ def resolve_function_parent_concepts(
|
|
|
49
54
|
|
|
50
55
|
|
|
51
56
|
def resolve_condition_parent_concepts(
|
|
52
|
-
condition:
|
|
53
|
-
) -> Tuple[List[
|
|
57
|
+
condition: BuildWhereClause,
|
|
58
|
+
) -> Tuple[List[BuildConcept], List[Tuple[BuildConcept, ...]]]:
|
|
54
59
|
base_existence = []
|
|
55
|
-
base_rows = []
|
|
60
|
+
base_rows: list[BuildConcept] = []
|
|
56
61
|
base_rows += condition.row_arguments
|
|
57
62
|
for ctuple in condition.existence_arguments:
|
|
58
63
|
base_existence.append(ctuple)
|
|
@@ -60,10 +65,10 @@ def resolve_condition_parent_concepts(
|
|
|
60
65
|
|
|
61
66
|
|
|
62
67
|
def resolve_filter_parent_concepts(
|
|
63
|
-
concept:
|
|
64
|
-
environment:
|
|
65
|
-
) -> Tuple[
|
|
66
|
-
if not isinstance(concept.lineage,
|
|
68
|
+
concept: BuildConcept,
|
|
69
|
+
environment: BuildEnvironment,
|
|
70
|
+
) -> Tuple[BuildConcept, List[BuildConcept], List[Tuple[BuildConcept, ...]]]:
|
|
71
|
+
if not isinstance(concept.lineage, (BuildFilterItem,)):
|
|
67
72
|
raise ValueError(
|
|
68
73
|
f"Concept {concept} lineage is not filter item, is {type(concept.lineage)}"
|
|
69
74
|
)
|
|
@@ -75,14 +80,14 @@ def resolve_filter_parent_concepts(
|
|
|
75
80
|
)
|
|
76
81
|
base_rows += condition_rows
|
|
77
82
|
base_existence += condition_existence
|
|
78
|
-
|
|
79
|
-
base_rows += [environment.concepts[c] for c in direct_parent.grain.components]
|
|
83
|
+
# this is required so that
|
|
80
84
|
if (
|
|
81
|
-
isinstance(direct_parent,
|
|
82
|
-
and direct_parent.purpose
|
|
85
|
+
isinstance(direct_parent, BuildConcept)
|
|
86
|
+
and direct_parent.purpose in (Purpose.PROPERTY, Purpose.METRIC)
|
|
83
87
|
and direct_parent.keys
|
|
84
88
|
):
|
|
85
89
|
base_rows += [environment.concepts[c] for c in direct_parent.keys]
|
|
90
|
+
|
|
86
91
|
if concept.lineage.where.existence_arguments:
|
|
87
92
|
return (
|
|
88
93
|
concept.lineage.content,
|
|
@@ -94,14 +99,14 @@ def resolve_filter_parent_concepts(
|
|
|
94
99
|
|
|
95
100
|
def gen_property_enrichment_node(
|
|
96
101
|
base_node: StrategyNode,
|
|
97
|
-
extra_properties: list[
|
|
98
|
-
|
|
102
|
+
extra_properties: list[BuildConcept],
|
|
103
|
+
history: History,
|
|
104
|
+
environment: BuildEnvironment,
|
|
99
105
|
g,
|
|
100
106
|
depth: int,
|
|
101
107
|
source_concepts,
|
|
102
108
|
log_lambda: Callable,
|
|
103
|
-
|
|
104
|
-
conditions: WhereClause | None = None,
|
|
109
|
+
conditions: BuildWhereClause | None = None,
|
|
105
110
|
):
|
|
106
111
|
required_keys: dict[str, set[str]] = defaultdict(set)
|
|
107
112
|
for x in extra_properties:
|
|
@@ -146,17 +151,17 @@ def gen_property_enrichment_node(
|
|
|
146
151
|
|
|
147
152
|
def gen_enrichment_node(
|
|
148
153
|
base_node: StrategyNode,
|
|
149
|
-
join_keys: List[
|
|
150
|
-
local_optional: list[
|
|
151
|
-
environment:
|
|
154
|
+
join_keys: List[BuildConcept],
|
|
155
|
+
local_optional: list[BuildConcept],
|
|
156
|
+
environment: BuildEnvironment,
|
|
152
157
|
g,
|
|
153
158
|
depth: int,
|
|
154
159
|
source_concepts,
|
|
155
160
|
log_lambda,
|
|
156
|
-
history: History
|
|
157
|
-
conditions:
|
|
161
|
+
history: History,
|
|
162
|
+
conditions: BuildWhereClause | None = None,
|
|
158
163
|
):
|
|
159
|
-
local_opts =
|
|
164
|
+
local_opts = LooseBuildConceptList(concepts=local_optional)
|
|
160
165
|
|
|
161
166
|
extra_required = [
|
|
162
167
|
x
|
|
@@ -165,7 +170,8 @@ def gen_enrichment_node(
|
|
|
165
170
|
]
|
|
166
171
|
|
|
167
172
|
# property lookup optimization
|
|
168
|
-
# this helps when evaluating a normalized star schema
|
|
173
|
+
# this helps create ergonomic merge nodes when evaluating a normalized star schema
|
|
174
|
+
# as we only want to lookup the missing properties based on the relevant keys
|
|
169
175
|
if all([x.purpose == Purpose.PROPERTY for x in extra_required]):
|
|
170
176
|
if all(
|
|
171
177
|
x.keys and all([key in base_node.output_lcl for key in x.keys])
|
|
@@ -177,16 +183,16 @@ def gen_enrichment_node(
|
|
|
177
183
|
return gen_property_enrichment_node(
|
|
178
184
|
base_node,
|
|
179
185
|
extra_required,
|
|
180
|
-
environment,
|
|
181
|
-
g,
|
|
182
|
-
depth,
|
|
183
|
-
source_concepts,
|
|
186
|
+
environment=environment,
|
|
187
|
+
g=g,
|
|
188
|
+
depth=depth,
|
|
189
|
+
source_concepts=source_concepts,
|
|
184
190
|
history=history,
|
|
185
191
|
conditions=conditions,
|
|
186
192
|
log_lambda=log_lambda,
|
|
187
193
|
)
|
|
188
194
|
log_lambda(
|
|
189
|
-
f"{str(type(base_node).__name__)} searching for join keys {
|
|
195
|
+
f"{str(type(base_node).__name__)} searching for join keys {LooseBuildConceptList(concepts=join_keys)} and extra required {local_opts}"
|
|
190
196
|
)
|
|
191
197
|
enrich_node: StrategyNode = source_concepts( # this fetches the parent + join keys
|
|
192
198
|
# to then connect to the rest of the query
|
|
@@ -208,7 +214,7 @@ def gen_enrichment_node(
|
|
|
208
214
|
non_hidden = [
|
|
209
215
|
x
|
|
210
216
|
for x in base_node.output_concepts
|
|
211
|
-
if x.address not in
|
|
217
|
+
if x.address not in base_node.hidden_concepts
|
|
212
218
|
]
|
|
213
219
|
return MergeNode(
|
|
214
220
|
input_concepts=unique(join_keys + extra_required + non_hidden, "address"),
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
3
|
from trilogy.constants import logger
|
|
4
|
-
from trilogy.core.models import
|
|
4
|
+
from trilogy.core.models.build import (
|
|
5
|
+
BuildConcept,
|
|
6
|
+
BuildFilterItem,
|
|
7
|
+
BuildGrain,
|
|
8
|
+
BuildWhereClause,
|
|
9
|
+
)
|
|
10
|
+
from trilogy.core.models.build_environment import BuildEnvironment
|
|
5
11
|
from trilogy.core.processing.node_generators.common import (
|
|
6
12
|
resolve_filter_parent_concepts,
|
|
7
13
|
)
|
|
@@ -16,28 +22,30 @@ from trilogy.core.processing.utility import is_scalar_condition, padding, unique
|
|
|
16
22
|
|
|
17
23
|
LOGGER_PREFIX = "[GEN_FILTER_NODE]"
|
|
18
24
|
|
|
25
|
+
FILTER_TYPES = (BuildFilterItem,)
|
|
26
|
+
|
|
19
27
|
|
|
20
28
|
def gen_filter_node(
|
|
21
|
-
concept:
|
|
22
|
-
local_optional: List[
|
|
23
|
-
environment:
|
|
29
|
+
concept: BuildConcept,
|
|
30
|
+
local_optional: List[BuildConcept],
|
|
31
|
+
environment: BuildEnvironment,
|
|
24
32
|
g,
|
|
25
33
|
depth: int,
|
|
26
34
|
source_concepts,
|
|
27
35
|
history: History | None = None,
|
|
28
|
-
conditions:
|
|
36
|
+
conditions: BuildWhereClause | None = None,
|
|
29
37
|
) -> StrategyNode | None:
|
|
30
38
|
immediate_parent, parent_row_concepts, parent_existence_concepts = (
|
|
31
39
|
resolve_filter_parent_concepts(concept, environment)
|
|
32
40
|
)
|
|
33
|
-
if not isinstance(concept.lineage,
|
|
34
|
-
raise SyntaxError('Filter node must have a
|
|
41
|
+
if not isinstance(concept.lineage, FILTER_TYPES):
|
|
42
|
+
raise SyntaxError('Filter node must have a filter type lineage"')
|
|
35
43
|
where = concept.lineage.where
|
|
36
44
|
|
|
37
|
-
optional_included: list[
|
|
45
|
+
optional_included: list[BuildConcept] = []
|
|
38
46
|
|
|
39
47
|
for x in local_optional:
|
|
40
|
-
if isinstance(x.lineage,
|
|
48
|
+
if isinstance(x.lineage, FILTER_TYPES):
|
|
41
49
|
if concept.lineage.where == where:
|
|
42
50
|
logger.info(
|
|
43
51
|
f"{padding(depth)}{LOGGER_PREFIX} fetching {x.lineage.content.address} as optional parent with same filter conditions "
|
|
@@ -48,7 +56,7 @@ def gen_filter_node(
|
|
|
48
56
|
if conditions and conditions == where:
|
|
49
57
|
optional_included.append(x)
|
|
50
58
|
logger.info(
|
|
51
|
-
f"{padding(depth)}{LOGGER_PREFIX} filter {concept.
|
|
59
|
+
f"{padding(depth)}{LOGGER_PREFIX} filter `{concept}` condition `{concept.lineage.where}` derived from {immediate_parent.address} row parents {[x.address for x in parent_row_concepts]} and {[[y.address] for x in parent_existence_concepts for y in x]} existence parents"
|
|
52
60
|
)
|
|
53
61
|
# we'll populate this with the row parent
|
|
54
62
|
# and the existence parent(s)
|
|
@@ -136,7 +144,7 @@ def gen_filter_node(
|
|
|
136
144
|
parent.add_existence_concepts(flattened_existence, False).set_output_concepts(
|
|
137
145
|
expected_output, False
|
|
138
146
|
)
|
|
139
|
-
parent.grain =
|
|
147
|
+
parent.grain = BuildGrain.from_concepts(
|
|
140
148
|
(
|
|
141
149
|
[environment.concepts[k] for k in immediate_parent.keys]
|
|
142
150
|
if immediate_parent.keys
|
|
@@ -162,7 +170,7 @@ def gen_filter_node(
|
|
|
162
170
|
output_concepts=[concept, immediate_parent] + parent_row_concepts,
|
|
163
171
|
environment=environment,
|
|
164
172
|
parents=core_parents,
|
|
165
|
-
grain=
|
|
173
|
+
grain=BuildGrain.from_concepts(
|
|
166
174
|
[immediate_parent] + parent_row_concepts,
|
|
167
175
|
),
|
|
168
176
|
preexisting_conditions=conditions.conditional if conditions else None,
|
|
@@ -185,7 +193,7 @@ def gen_filter_node(
|
|
|
185
193
|
)
|
|
186
194
|
return filter_node
|
|
187
195
|
|
|
188
|
-
enrich_node = source_concepts( # this fetches the parent + join keys
|
|
196
|
+
enrich_node: StrategyNode = source_concepts( # this fetches the parent + join keys
|
|
189
197
|
# to then connect to the rest of the query
|
|
190
198
|
mandatory_list=[immediate_parent] + parent_row_concepts + local_optional,
|
|
191
199
|
environment=environment,
|
|
@@ -194,6 +202,11 @@ def gen_filter_node(
|
|
|
194
202
|
history=history,
|
|
195
203
|
conditions=conditions,
|
|
196
204
|
)
|
|
205
|
+
if not enrich_node:
|
|
206
|
+
logger.error(
|
|
207
|
+
f"{padding(depth)}{LOGGER_PREFIX} filter node enrichment node could not be found"
|
|
208
|
+
)
|
|
209
|
+
return filter_node
|
|
197
210
|
logger.info(
|
|
198
211
|
f"{padding(depth)}{LOGGER_PREFIX} returning filter node and enrich node with {enrich_node.output_concepts} and {enrich_node.input_concepts}"
|
|
199
212
|
)
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
3
|
from trilogy.constants import logger
|
|
4
|
-
from trilogy.core.models import (
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
WhereClause,
|
|
4
|
+
from trilogy.core.models.build import (
|
|
5
|
+
BuildAggregateWrapper,
|
|
6
|
+
BuildConcept,
|
|
7
|
+
BuildFunction,
|
|
8
|
+
BuildGrain,
|
|
9
|
+
BuildWhereClause,
|
|
10
|
+
LooseBuildConceptList,
|
|
12
11
|
)
|
|
12
|
+
from trilogy.core.models.build_environment import BuildEnvironment
|
|
13
13
|
from trilogy.core.processing.node_generators.common import (
|
|
14
14
|
gen_enrichment_node,
|
|
15
15
|
resolve_function_parent_concepts,
|
|
@@ -22,22 +22,22 @@ LOGGER_PREFIX = "[GEN_GROUP_NODE]"
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def gen_group_node(
|
|
25
|
-
concept:
|
|
26
|
-
local_optional: List[
|
|
27
|
-
environment:
|
|
25
|
+
concept: BuildConcept,
|
|
26
|
+
local_optional: List[BuildConcept],
|
|
27
|
+
environment: BuildEnvironment,
|
|
28
28
|
g,
|
|
29
29
|
depth: int,
|
|
30
30
|
source_concepts,
|
|
31
|
-
history: History
|
|
32
|
-
conditions:
|
|
31
|
+
history: History,
|
|
32
|
+
conditions: BuildWhereClause | None = None,
|
|
33
33
|
) -> StrategyNode | None:
|
|
34
34
|
# aggregates MUST always group to the proper grain
|
|
35
35
|
# except when the
|
|
36
|
-
parent_concepts: List[
|
|
36
|
+
parent_concepts: List[BuildConcept] = unique(
|
|
37
37
|
resolve_function_parent_concepts(concept, environment=environment), "address"
|
|
38
38
|
)
|
|
39
39
|
logger.info(
|
|
40
|
-
f"{padding(depth)}{LOGGER_PREFIX} parent concepts are {[x.address for x in parent_concepts]} from group grain {concept.grain}"
|
|
40
|
+
f"{padding(depth)}{LOGGER_PREFIX} parent concepts for {concept} are {[x.address for x in parent_concepts]} from group grain {concept.grain}"
|
|
41
41
|
)
|
|
42
42
|
|
|
43
43
|
# if the aggregation has a grain, we need to ensure these are the ONLY optional in the output of the select
|
|
@@ -53,16 +53,18 @@ def gen_group_node(
|
|
|
53
53
|
output_concepts += grain_components
|
|
54
54
|
for possible_agg in local_optional:
|
|
55
55
|
|
|
56
|
-
if not isinstance(
|
|
56
|
+
if not isinstance(
|
|
57
|
+
possible_agg.lineage,
|
|
58
|
+
(BuildAggregateWrapper, BuildFunction),
|
|
59
|
+
):
|
|
57
60
|
continue
|
|
58
|
-
logger.info(possible_agg)
|
|
59
61
|
if possible_agg.grain and possible_agg.grain != concept.grain:
|
|
60
62
|
logger.info(
|
|
61
63
|
f"{padding(depth)}{LOGGER_PREFIX} mismatched equivalent group by with grain {possible_agg.grain} for {concept.address}"
|
|
62
64
|
)
|
|
63
65
|
|
|
64
66
|
if possible_agg.grain and possible_agg.grain == concept.grain:
|
|
65
|
-
agg_parents: List[
|
|
67
|
+
agg_parents: List[BuildConcept] = resolve_function_parent_concepts(
|
|
66
68
|
possible_agg,
|
|
67
69
|
environment=environment,
|
|
68
70
|
)
|
|
@@ -73,7 +75,7 @@ def gen_group_node(
|
|
|
73
75
|
logger.info(
|
|
74
76
|
f"{padding(depth)}{LOGGER_PREFIX} found equivalent group by optional concept {possible_agg.address} for {concept.address}"
|
|
75
77
|
)
|
|
76
|
-
elif
|
|
78
|
+
elif BuildGrain.from_concepts(agg_parents) == BuildGrain.from_concepts(
|
|
77
79
|
parent_concepts
|
|
78
80
|
):
|
|
79
81
|
extra = [x for x in agg_parents if x.address not in parent_concepts]
|
|
@@ -84,11 +86,11 @@ def gen_group_node(
|
|
|
84
86
|
)
|
|
85
87
|
else:
|
|
86
88
|
logger.info(
|
|
87
|
-
f"{padding(depth)}{LOGGER_PREFIX} mismatched grain {
|
|
89
|
+
f"{padding(depth)}{LOGGER_PREFIX} cannot include optional agg; mismatched grain {BuildGrain.from_concepts(agg_parents)} vs {BuildGrain.from_concepts(parent_concepts)}"
|
|
88
90
|
)
|
|
89
91
|
if parent_concepts:
|
|
90
92
|
logger.info(
|
|
91
|
-
f"{padding(depth)}{LOGGER_PREFIX} fetching group node parents {
|
|
93
|
+
f"{padding(depth)}{LOGGER_PREFIX} fetching group node parents {LooseBuildConceptList(concepts=parent_concepts)}"
|
|
92
94
|
)
|
|
93
95
|
parent_concepts = unique(parent_concepts, "address")
|
|
94
96
|
parent = source_concepts(
|
|
@@ -124,6 +126,9 @@ def gen_group_node(
|
|
|
124
126
|
# early exit if no optional
|
|
125
127
|
|
|
126
128
|
if not local_optional:
|
|
129
|
+
logger.info(
|
|
130
|
+
f"{padding(depth)}{LOGGER_PREFIX} no optional concepts, returning group node"
|
|
131
|
+
)
|
|
127
132
|
return group_node
|
|
128
133
|
missing_optional = [
|
|
129
134
|
x.address for x in local_optional if x.address not in group_node.output_concepts
|
|
@@ -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
|
|
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 (
|
|
6
7
|
GroupNode,
|
|
7
8
|
History,
|
|
@@ -14,20 +15,22 @@ LOGGER_PREFIX = "[GEN_GROUP_TO_NODE]"
|
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
def gen_group_to_node(
|
|
17
|
-
concept:
|
|
18
|
+
concept: BuildConcept,
|
|
18
19
|
local_optional,
|
|
19
|
-
environment:
|
|
20
|
+
environment: BuildEnvironment,
|
|
20
21
|
g,
|
|
21
22
|
depth: int,
|
|
22
23
|
source_concepts,
|
|
23
24
|
history: History | None = None,
|
|
24
|
-
conditions:
|
|
25
|
+
conditions: BuildWhereClause | None = None,
|
|
25
26
|
) -> GroupNode | MergeNode:
|
|
26
27
|
# aggregates MUST always group to the proper grain
|
|
27
|
-
if not isinstance(concept.lineage,
|
|
28
|
-
raise SyntaxError(
|
|
28
|
+
if not isinstance(concept.lineage, BuildFunction):
|
|
29
|
+
raise SyntaxError(
|
|
30
|
+
f"Group to should have function lineage, is {type(concept.lineage)}"
|
|
31
|
+
)
|
|
29
32
|
group_arg = concept.lineage.arguments[0]
|
|
30
|
-
parent_concepts: List[
|
|
33
|
+
parent_concepts: List[BuildConcept] = concept.lineage.concept_arguments
|
|
31
34
|
logger.info(
|
|
32
35
|
f"{padding(depth)}{LOGGER_PREFIX} group by node has required parents {[x.address for x in parent_concepts]}"
|
|
33
36
|
)
|
|
@@ -49,9 +52,9 @@ def gen_group_to_node(
|
|
|
49
52
|
parents=parents,
|
|
50
53
|
depth=depth,
|
|
51
54
|
preexisting_conditions=conditions.conditional if conditions else None,
|
|
52
|
-
hidden_concepts=(
|
|
53
|
-
[group_arg]
|
|
54
|
-
if isinstance(group_arg,
|
|
55
|
+
hidden_concepts=set(
|
|
56
|
+
[group_arg.address]
|
|
57
|
+
if isinstance(group_arg, BuildConcept)
|
|
55
58
|
and group_arg.address not in local_optional
|
|
56
59
|
else []
|
|
57
60
|
),
|