pytrilogy 0.0.2.17__py3-none-any.whl → 0.0.2.19__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.17.dist-info → pytrilogy-0.0.2.19.dist-info}/METADATA +12 -8
- {pytrilogy-0.0.2.17.dist-info → pytrilogy-0.0.2.19.dist-info}/RECORD +40 -39
- trilogy/__init__.py +1 -1
- trilogy/constants.py +1 -1
- trilogy/core/enums.py +1 -0
- trilogy/core/functions.py +11 -0
- trilogy/core/models.py +89 -47
- trilogy/core/optimization.py +15 -9
- trilogy/core/processing/concept_strategies_v3.py +372 -145
- trilogy/core/processing/node_generators/basic_node.py +27 -55
- trilogy/core/processing/node_generators/common.py +6 -7
- trilogy/core/processing/node_generators/filter_node.py +28 -31
- trilogy/core/processing/node_generators/group_node.py +27 -5
- trilogy/core/processing/node_generators/group_to_node.py +3 -1
- trilogy/core/processing/node_generators/multiselect_node.py +3 -0
- trilogy/core/processing/node_generators/node_merge_node.py +14 -10
- trilogy/core/processing/node_generators/rowset_node.py +12 -12
- trilogy/core/processing/node_generators/select_merge_node.py +317 -0
- trilogy/core/processing/node_generators/select_node.py +7 -511
- trilogy/core/processing/node_generators/unnest_node.py +4 -3
- trilogy/core/processing/node_generators/window_node.py +12 -37
- trilogy/core/processing/nodes/__init__.py +0 -2
- trilogy/core/processing/nodes/base_node.py +69 -20
- trilogy/core/processing/nodes/filter_node.py +3 -0
- trilogy/core/processing/nodes/group_node.py +18 -17
- trilogy/core/processing/nodes/merge_node.py +4 -10
- trilogy/core/processing/nodes/select_node_v2.py +28 -14
- trilogy/core/processing/nodes/window_node.py +1 -2
- trilogy/core/processing/utility.py +51 -4
- trilogy/core/query_processor.py +17 -73
- trilogy/dialect/base.py +7 -3
- trilogy/dialect/duckdb.py +4 -1
- trilogy/dialect/sql_server.py +3 -3
- trilogy/hooks/query_debugger.py +5 -3
- trilogy/parsing/parse_engine.py +66 -38
- trilogy/parsing/trilogy.lark +2 -1
- {pytrilogy-0.0.2.17.dist-info → pytrilogy-0.0.2.19.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.17.dist-info → pytrilogy-0.0.2.19.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.2.17.dist-info → pytrilogy-0.0.2.19.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.2.17.dist-info → pytrilogy-0.0.2.19.dist-info}/top_level.txt +0 -0
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
# directly select out a basic derivation
|
|
2
2
|
from typing import List
|
|
3
3
|
|
|
4
|
-
from trilogy.core.models import
|
|
5
|
-
Concept,
|
|
6
|
-
)
|
|
4
|
+
from trilogy.core.models import Concept, WhereClause
|
|
7
5
|
from trilogy.core.processing.nodes import StrategyNode, History
|
|
8
6
|
from trilogy.core.processing.node_generators.common import (
|
|
9
7
|
resolve_function_parent_concepts,
|
|
10
8
|
)
|
|
11
|
-
from trilogy.utility import unique
|
|
12
9
|
from trilogy.constants import logger
|
|
13
10
|
from trilogy.core.enums import SourceType
|
|
14
|
-
from itertools import combinations
|
|
15
11
|
|
|
16
12
|
LOGGER_PREFIX = "[GEN_BASIC_NODE]"
|
|
17
13
|
|
|
@@ -24,6 +20,7 @@ def gen_basic_node(
|
|
|
24
20
|
depth: int,
|
|
25
21
|
source_concepts,
|
|
26
22
|
history: History | None = None,
|
|
23
|
+
conditions: WhereClause | None = None,
|
|
27
24
|
):
|
|
28
25
|
depth_prefix = "\t" * depth
|
|
29
26
|
parent_concepts = resolve_function_parent_concepts(concept)
|
|
@@ -32,10 +29,6 @@ def gen_basic_node(
|
|
|
32
29
|
f"{depth_prefix}{LOGGER_PREFIX} basic node for {concept} has parents {[x.address for x in parent_concepts]}"
|
|
33
30
|
)
|
|
34
31
|
|
|
35
|
-
local_optional_redundant = [x for x in local_optional if x in parent_concepts]
|
|
36
|
-
attempts: List[tuple[list[Concept], list[Concept]]] = [
|
|
37
|
-
(parent_concepts, [concept] + local_optional_redundant)
|
|
38
|
-
]
|
|
39
32
|
equivalent_optional = [
|
|
40
33
|
x
|
|
41
34
|
for x in local_optional
|
|
@@ -44,56 +37,35 @@ def gen_basic_node(
|
|
|
44
37
|
non_equivalent_optional = [
|
|
45
38
|
x for x in local_optional if x not in equivalent_optional
|
|
46
39
|
]
|
|
40
|
+
parent_node: StrategyNode = source_concepts(
|
|
41
|
+
mandatory_list=parent_concepts + non_equivalent_optional,
|
|
42
|
+
environment=environment,
|
|
43
|
+
g=g,
|
|
44
|
+
depth=depth + 1,
|
|
45
|
+
history=history,
|
|
46
|
+
conditions=conditions,
|
|
47
|
+
)
|
|
47
48
|
|
|
48
|
-
if
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
for optional_set in combos:
|
|
52
|
-
attempts.append(
|
|
53
|
-
(
|
|
54
|
-
unique(parent_concepts + list(optional_set), "address"),
|
|
55
|
-
list(optional_set) + [concept],
|
|
56
|
-
)
|
|
57
|
-
)
|
|
58
|
-
# check for the concept by itself
|
|
59
|
-
for attempt, basic_output in reversed(attempts):
|
|
60
|
-
partials = []
|
|
61
|
-
attempt = unique(attempt, "address")
|
|
62
|
-
parent_node: StrategyNode = source_concepts(
|
|
63
|
-
mandatory_list=attempt,
|
|
64
|
-
environment=environment,
|
|
65
|
-
g=g,
|
|
66
|
-
depth=depth + 1,
|
|
67
|
-
history=history,
|
|
49
|
+
if not parent_node:
|
|
50
|
+
logger.info(
|
|
51
|
+
f"{depth_prefix}{LOGGER_PREFIX} No basic node could be generated for {concept}"
|
|
68
52
|
)
|
|
53
|
+
return None
|
|
69
54
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
for x in basic_output:
|
|
75
|
-
sources = [p for p in parents if x in p.output_concepts]
|
|
76
|
-
if not sources:
|
|
77
|
-
continue
|
|
78
|
-
if all(x in source.partial_concepts for source in sources):
|
|
79
|
-
partials.append(x)
|
|
55
|
+
parent_node.source_type = SourceType.BASIC
|
|
56
|
+
parent_node.add_output_concept(concept)
|
|
57
|
+
for x in equivalent_optional:
|
|
58
|
+
parent_node.add_output_concept(x)
|
|
80
59
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
60
|
+
parent_node.remove_output_concepts(
|
|
61
|
+
[
|
|
62
|
+
x
|
|
63
|
+
for x in parent_node.output_concepts
|
|
64
|
+
if x.address not in [concept] + local_optional
|
|
65
|
+
]
|
|
66
|
+
)
|
|
84
67
|
|
|
85
|
-
parent_node.remove_output_concepts(
|
|
86
|
-
[
|
|
87
|
-
x
|
|
88
|
-
for x in parent_node.output_concepts
|
|
89
|
-
if x.address not in [y.address for y in basic_output]
|
|
90
|
-
]
|
|
91
|
-
)
|
|
92
|
-
logger.info(
|
|
93
|
-
f"{depth_prefix}{LOGGER_PREFIX} Returning basic select for {concept} with attempted extra {[x.address for x in attempt]}, output {[x.address for x in parent_node.output_concepts]}"
|
|
94
|
-
)
|
|
95
|
-
return parent_node
|
|
96
68
|
logger.info(
|
|
97
|
-
f"{depth_prefix}{LOGGER_PREFIX}
|
|
69
|
+
f"{depth_prefix}{LOGGER_PREFIX} Returning basic select for {concept}: output {[x.address for x in parent_node.output_concepts]}"
|
|
98
70
|
)
|
|
99
|
-
return
|
|
71
|
+
return parent_node
|
|
@@ -136,6 +136,7 @@ def gen_property_enrichment_node(
|
|
|
136
136
|
base_node,
|
|
137
137
|
]
|
|
138
138
|
+ final_nodes,
|
|
139
|
+
preexisting_conditions=conditions.conditional if conditions else None,
|
|
139
140
|
)
|
|
140
141
|
|
|
141
142
|
|
|
@@ -154,11 +155,6 @@ def gen_enrichment_node(
|
|
|
154
155
|
|
|
155
156
|
local_opts = LooseConceptList(concepts=local_optional)
|
|
156
157
|
|
|
157
|
-
if local_opts.issubset(base_node.output_lcl):
|
|
158
|
-
log_lambda(
|
|
159
|
-
f"{str(type(base_node).__name__)} has all optional { base_node.output_lcl}, skipping enrichmennt"
|
|
160
|
-
)
|
|
161
|
-
return base_node
|
|
162
158
|
extra_required = [
|
|
163
159
|
x
|
|
164
160
|
for x in local_opts
|
|
@@ -186,13 +182,15 @@ def gen_enrichment_node(
|
|
|
186
182
|
conditions=conditions,
|
|
187
183
|
log_lambda=log_lambda,
|
|
188
184
|
)
|
|
189
|
-
|
|
185
|
+
log_lambda(
|
|
186
|
+
f"{str(type(base_node).__name__)} searching for join keys {LooseConceptList(concepts=join_keys)} and extra required {local_opts}"
|
|
187
|
+
)
|
|
190
188
|
enrich_node: StrategyNode = source_concepts( # this fetches the parent + join keys
|
|
191
189
|
# to then connect to the rest of the query
|
|
192
190
|
mandatory_list=join_keys + extra_required,
|
|
193
191
|
environment=environment,
|
|
194
192
|
g=g,
|
|
195
|
-
depth=depth,
|
|
193
|
+
depth=depth + 1,
|
|
196
194
|
history=history,
|
|
197
195
|
conditions=conditions,
|
|
198
196
|
)
|
|
@@ -216,6 +214,7 @@ def gen_enrichment_node(
|
|
|
216
214
|
g=g,
|
|
217
215
|
parents=[enrich_node, base_node],
|
|
218
216
|
force_group=False,
|
|
217
|
+
preexisting_conditions=conditions.conditional if conditions else None,
|
|
219
218
|
)
|
|
220
219
|
|
|
221
220
|
|
|
@@ -37,6 +37,7 @@ def gen_filter_node(
|
|
|
37
37
|
where = concept.lineage.where
|
|
38
38
|
|
|
39
39
|
optional_included: list[Concept] = []
|
|
40
|
+
|
|
40
41
|
for x in local_optional:
|
|
41
42
|
if isinstance(x.lineage, FilterItem):
|
|
42
43
|
if concept.lineage.where == where:
|
|
@@ -45,17 +46,23 @@ def gen_filter_node(
|
|
|
45
46
|
)
|
|
46
47
|
parent_row_concepts.append(x.lineage.content)
|
|
47
48
|
optional_included.append(x)
|
|
49
|
+
continue
|
|
50
|
+
if conditions and conditions == where:
|
|
51
|
+
optional_included.append(x)
|
|
48
52
|
logger.info(
|
|
49
53
|
f"{padding(depth)}{LOGGER_PREFIX} filter {concept.address} 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"
|
|
50
54
|
)
|
|
55
|
+
# we'll populate this with the row parent
|
|
56
|
+
# and the existence parent(s)
|
|
51
57
|
core_parents = []
|
|
58
|
+
|
|
52
59
|
row_parent: StrategyNode = source_concepts(
|
|
53
60
|
mandatory_list=parent_row_concepts,
|
|
54
61
|
environment=environment,
|
|
55
62
|
g=g,
|
|
56
63
|
depth=depth + 1,
|
|
57
64
|
history=history,
|
|
58
|
-
conditions=conditions,
|
|
65
|
+
# conditions=conditions,
|
|
59
66
|
)
|
|
60
67
|
|
|
61
68
|
flattened_existence = [x for y in parent_existence_concepts for x in y]
|
|
@@ -101,6 +108,9 @@ def gen_filter_node(
|
|
|
101
108
|
)
|
|
102
109
|
optimized_pushdown = True
|
|
103
110
|
if optimized_pushdown:
|
|
111
|
+
logger.info(
|
|
112
|
+
f"{padding(depth)}{LOGGER_PREFIX} returning optimized filter node with pushdown to parent with condition {where.conditional}"
|
|
113
|
+
)
|
|
104
114
|
if isinstance(row_parent, SelectNode):
|
|
105
115
|
logger.info(
|
|
106
116
|
f"{padding(depth)}{LOGGER_PREFIX} nesting select node in strategy node"
|
|
@@ -110,16 +120,10 @@ def gen_filter_node(
|
|
|
110
120
|
output_concepts=[concept] + row_parent.output_concepts,
|
|
111
121
|
environment=row_parent.environment,
|
|
112
122
|
g=row_parent.g,
|
|
113
|
-
parents=[row_parent]
|
|
123
|
+
parents=[row_parent],
|
|
114
124
|
depth=row_parent.depth,
|
|
115
125
|
partial_concepts=row_parent.partial_concepts,
|
|
116
126
|
force_group=False,
|
|
117
|
-
conditions=(
|
|
118
|
-
row_parent.conditions + where.conditional
|
|
119
|
-
if row_parent.conditions
|
|
120
|
-
else where.conditional
|
|
121
|
-
),
|
|
122
|
-
existence_concepts=row_parent.existence_concepts,
|
|
123
127
|
)
|
|
124
128
|
else:
|
|
125
129
|
parent = row_parent
|
|
@@ -127,13 +131,14 @@ def gen_filter_node(
|
|
|
127
131
|
expected_output = [concept] + [
|
|
128
132
|
x
|
|
129
133
|
for x in local_optional
|
|
130
|
-
if x.address in [y
|
|
131
|
-
or x.address in [y
|
|
134
|
+
if x.address in [y for y in parent.output_concepts]
|
|
135
|
+
or x.address in [y for y in optional_included]
|
|
132
136
|
]
|
|
133
137
|
parent.add_parents(core_parents)
|
|
134
138
|
parent.add_condition(where.conditional)
|
|
135
|
-
parent.add_existence_concepts(flattened_existence)
|
|
136
|
-
|
|
139
|
+
parent.add_existence_concepts(flattened_existence, False).set_output_concepts(
|
|
140
|
+
expected_output, False
|
|
141
|
+
)
|
|
137
142
|
parent.grain = Grain(
|
|
138
143
|
components=(
|
|
139
144
|
list(immediate_parent.keys)
|
|
@@ -147,10 +152,6 @@ def gen_filter_node(
|
|
|
147
152
|
]
|
|
148
153
|
)
|
|
149
154
|
parent.rebuild_cache()
|
|
150
|
-
|
|
151
|
-
logger.info(
|
|
152
|
-
f"{padding(depth)}{LOGGER_PREFIX} returning optimized filter node with pushdown to parent with condition {where.conditional}"
|
|
153
|
-
)
|
|
154
155
|
filter_node = parent
|
|
155
156
|
else:
|
|
156
157
|
core_parents.append(row_parent)
|
|
@@ -167,26 +168,24 @@ def gen_filter_node(
|
|
|
167
168
|
grain=Grain(
|
|
168
169
|
components=[immediate_parent] + parent_row_concepts,
|
|
169
170
|
),
|
|
171
|
+
preexisting_conditions=conditions.conditional if conditions else None,
|
|
170
172
|
)
|
|
171
173
|
|
|
172
174
|
if not local_optional or all(
|
|
173
|
-
[
|
|
174
|
-
x.address in [y.address for y in filter_node.output_concepts]
|
|
175
|
-
for x in local_optional
|
|
176
|
-
]
|
|
175
|
+
[x.address in filter_node.output_concepts for x in local_optional]
|
|
177
176
|
):
|
|
178
|
-
|
|
179
|
-
x
|
|
180
|
-
for x in filter_node.output_concepts
|
|
181
|
-
if x.address in [y.address for y in local_optional]
|
|
177
|
+
optional_outputs = [
|
|
178
|
+
x for x in filter_node.output_concepts if x.address in local_optional
|
|
182
179
|
]
|
|
183
180
|
logger.info(
|
|
184
|
-
f"{padding(depth)}{LOGGER_PREFIX} no extra enrichment needed for filter node"
|
|
181
|
+
f"{padding(depth)}{LOGGER_PREFIX} no extra enrichment needed for filter node, has all of {[x.address for x in local_optional]}"
|
|
182
|
+
)
|
|
183
|
+
filter_node.set_output_concepts(
|
|
184
|
+
[
|
|
185
|
+
concept,
|
|
186
|
+
]
|
|
187
|
+
+ optional_outputs
|
|
185
188
|
)
|
|
186
|
-
filter_node.output_concepts = [
|
|
187
|
-
concept,
|
|
188
|
-
] + outputs
|
|
189
|
-
filter_node.rebuild_cache()
|
|
190
189
|
return filter_node
|
|
191
190
|
|
|
192
191
|
enrich_node = source_concepts( # this fetches the parent + join keys
|
|
@@ -198,8 +197,6 @@ def gen_filter_node(
|
|
|
198
197
|
history=history,
|
|
199
198
|
conditions=conditions,
|
|
200
199
|
)
|
|
201
|
-
if not enrich_node:
|
|
202
|
-
return filter_node
|
|
203
200
|
return MergeNode(
|
|
204
201
|
input_concepts=[concept, immediate_parent] + local_optional,
|
|
205
202
|
output_concepts=[
|
|
@@ -5,6 +5,7 @@ from trilogy.core.models import (
|
|
|
5
5
|
WhereClause,
|
|
6
6
|
Function,
|
|
7
7
|
AggregateWrapper,
|
|
8
|
+
Grain,
|
|
8
9
|
)
|
|
9
10
|
from trilogy.utility import unique
|
|
10
11
|
from trilogy.core.processing.nodes import GroupNode, StrategyNode, History
|
|
@@ -30,7 +31,7 @@ def gen_group_node(
|
|
|
30
31
|
source_concepts,
|
|
31
32
|
history: History | None = None,
|
|
32
33
|
conditions: WhereClause | None = None,
|
|
33
|
-
):
|
|
34
|
+
) -> StrategyNode | None:
|
|
34
35
|
# aggregates MUST always group to the proper grain
|
|
35
36
|
# except when the
|
|
36
37
|
parent_concepts: List[Concept] = unique(
|
|
@@ -50,9 +51,9 @@ def gen_group_node(
|
|
|
50
51
|
parent_concepts += grain_components
|
|
51
52
|
output_concepts += grain_components
|
|
52
53
|
for possible_agg in local_optional:
|
|
54
|
+
if not isinstance(possible_agg.lineage, (AggregateWrapper, Function)):
|
|
55
|
+
continue
|
|
53
56
|
if possible_agg.grain and possible_agg.grain == concept.grain:
|
|
54
|
-
if not isinstance(possible_agg.lineage, (AggregateWrapper, Function)):
|
|
55
|
-
continue
|
|
56
57
|
agg_parents: List[Concept] = resolve_function_parent_concepts(
|
|
57
58
|
possible_agg
|
|
58
59
|
)
|
|
@@ -60,7 +61,16 @@ def gen_group_node(
|
|
|
60
61
|
set([x.address for x in parent_concepts])
|
|
61
62
|
):
|
|
62
63
|
output_concepts.append(possible_agg)
|
|
63
|
-
|
|
64
|
+
logger.info(
|
|
65
|
+
f"{padding(depth)}{LOGGER_PREFIX} found equivalent group by optional concept {possible_agg.address} for {concept.address}"
|
|
66
|
+
)
|
|
67
|
+
elif Grain(components=agg_parents) == Grain(components=parent_concepts):
|
|
68
|
+
extra = [x for x in agg_parents if x.address not in parent_concepts]
|
|
69
|
+
parent_concepts += extra
|
|
70
|
+
output_concepts.append(possible_agg)
|
|
71
|
+
logger.info(
|
|
72
|
+
f"{padding(depth)}{LOGGER_PREFIX} found equivalent group by optional concept {possible_agg.address} for {concept.address}"
|
|
73
|
+
)
|
|
64
74
|
if parent_concepts:
|
|
65
75
|
logger.info(
|
|
66
76
|
f"{padding(depth)}{LOGGER_PREFIX} fetching group node parents {LooseConceptList(concepts=parent_concepts)}"
|
|
@@ -94,12 +104,24 @@ def gen_group_node(
|
|
|
94
104
|
g=g,
|
|
95
105
|
parents=parents,
|
|
96
106
|
depth=depth,
|
|
107
|
+
preexisting_conditions=conditions.conditional if conditions else None,
|
|
97
108
|
)
|
|
98
109
|
|
|
99
110
|
# early exit if no optional
|
|
111
|
+
|
|
100
112
|
if not local_optional:
|
|
101
113
|
return group_node
|
|
102
|
-
|
|
114
|
+
missing_optional = [
|
|
115
|
+
x.address for x in local_optional if x.address not in group_node.output_concepts
|
|
116
|
+
]
|
|
117
|
+
if not missing_optional:
|
|
118
|
+
logger.info(
|
|
119
|
+
f"{padding(depth)}{LOGGER_PREFIX} no extra enrichment needed for group node, has all of {[x.address for x in local_optional]}"
|
|
120
|
+
)
|
|
121
|
+
return group_node
|
|
122
|
+
logger.info(
|
|
123
|
+
f"{padding(depth)}{LOGGER_PREFIX} group node requires enrichment, missing {missing_optional}"
|
|
124
|
+
)
|
|
103
125
|
return gen_enrichment_node(
|
|
104
126
|
group_node,
|
|
105
127
|
join_keys=group_key_parents,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from trilogy.core.models import Concept, Environment, Function
|
|
1
|
+
from trilogy.core.models import Concept, Environment, Function, WhereClause
|
|
2
2
|
from trilogy.core.processing.nodes import (
|
|
3
3
|
GroupNode,
|
|
4
4
|
StrategyNode,
|
|
@@ -21,6 +21,7 @@ def gen_group_to_node(
|
|
|
21
21
|
depth: int,
|
|
22
22
|
source_concepts,
|
|
23
23
|
history: History | None = None,
|
|
24
|
+
conditions: WhereClause | None = None,
|
|
24
25
|
) -> GroupNode | MergeNode:
|
|
25
26
|
# aggregates MUST always group to the proper grain
|
|
26
27
|
if not isinstance(concept.lineage, Function):
|
|
@@ -36,6 +37,7 @@ def gen_group_to_node(
|
|
|
36
37
|
g=g,
|
|
37
38
|
depth=depth + 1,
|
|
38
39
|
history=history,
|
|
40
|
+
conditions=conditions,
|
|
39
41
|
)
|
|
40
42
|
]
|
|
41
43
|
|
|
@@ -2,6 +2,7 @@ from trilogy.core.models import (
|
|
|
2
2
|
Concept,
|
|
3
3
|
Environment,
|
|
4
4
|
MultiSelectStatement,
|
|
5
|
+
WhereClause,
|
|
5
6
|
)
|
|
6
7
|
from trilogy.core.processing.nodes import MergeNode, NodeJoin, History
|
|
7
8
|
from trilogy.core.processing.nodes.base_node import concept_list_to_grain, StrategyNode
|
|
@@ -58,6 +59,7 @@ def gen_multiselect_node(
|
|
|
58
59
|
depth: int,
|
|
59
60
|
source_concepts,
|
|
60
61
|
history: History | None = None,
|
|
62
|
+
conditions: WhereClause | None = None,
|
|
61
63
|
) -> MergeNode | None:
|
|
62
64
|
if not isinstance(concept.lineage, MultiSelectStatement):
|
|
63
65
|
logger.info(
|
|
@@ -157,6 +159,7 @@ def gen_multiselect_node(
|
|
|
157
159
|
g=g,
|
|
158
160
|
depth=depth + 1,
|
|
159
161
|
history=history,
|
|
162
|
+
conditions=conditions,
|
|
160
163
|
)
|
|
161
164
|
if not enrich_node:
|
|
162
165
|
logger.info(
|
|
@@ -135,7 +135,9 @@ def determine_induced_minimal_nodes(
|
|
|
135
135
|
return final
|
|
136
136
|
|
|
137
137
|
|
|
138
|
-
def detect_ambiguity_and_raise(
|
|
138
|
+
def detect_ambiguity_and_raise(
|
|
139
|
+
all_concepts: list[Concept], reduced_concept_sets: list[set[str]]
|
|
140
|
+
) -> None:
|
|
139
141
|
final_candidates: list[set[str]] = []
|
|
140
142
|
common: set[str] = set()
|
|
141
143
|
# find all values that show up in every join_additions
|
|
@@ -146,7 +148,6 @@ def detect_ambiguity_and_raise(all_concepts, reduced_concept_sets) -> None:
|
|
|
146
148
|
common = common.intersection(ja)
|
|
147
149
|
if all(set(ja).issubset(y) for y in reduced_concept_sets):
|
|
148
150
|
final_candidates.append(ja)
|
|
149
|
-
|
|
150
151
|
if not final_candidates:
|
|
151
152
|
filtered_paths = [x.difference(common) for x in reduced_concept_sets]
|
|
152
153
|
raise AmbiguousRelationshipResolutionException(
|
|
@@ -197,6 +198,9 @@ def resolve_weak_components(
|
|
|
197
198
|
for c in all_concepts
|
|
198
199
|
if "__preql_internal" not in c.address
|
|
199
200
|
]
|
|
201
|
+
synonyms: list[Concept] = []
|
|
202
|
+
for x in all_concepts:
|
|
203
|
+
synonyms += x.pseudonyms.values()
|
|
200
204
|
while break_flag is not True:
|
|
201
205
|
count += 1
|
|
202
206
|
if count > AMBIGUITY_CHECK_LIMIT:
|
|
@@ -222,13 +226,9 @@ def resolve_weak_components(
|
|
|
222
226
|
for node in g.nodes
|
|
223
227
|
if node.startswith("c~")
|
|
224
228
|
]
|
|
225
|
-
new = [
|
|
226
|
-
x
|
|
227
|
-
for x in all_graph_concepts
|
|
228
|
-
if x.address not in [y.address for y in all_concepts]
|
|
229
|
-
]
|
|
229
|
+
new = [x for x in all_graph_concepts if x.address not in all_concepts]
|
|
230
230
|
|
|
231
|
-
new_addresses = set([x.address for x in new])
|
|
231
|
+
new_addresses = set([x.address for x in new if x.address not in synonyms])
|
|
232
232
|
if not new:
|
|
233
233
|
break_flag = True
|
|
234
234
|
# remove our new nodes for the next search path
|
|
@@ -299,7 +299,7 @@ def subgraphs_to_merge_node(
|
|
|
299
299
|
g=g,
|
|
300
300
|
depth=depth + 1,
|
|
301
301
|
history=history,
|
|
302
|
-
conditions=search_conditions,
|
|
302
|
+
# conditions=search_conditions,
|
|
303
303
|
)
|
|
304
304
|
if not parent:
|
|
305
305
|
logger.info(
|
|
@@ -324,7 +324,9 @@ def subgraphs_to_merge_node(
|
|
|
324
324
|
g=g,
|
|
325
325
|
parents=parents,
|
|
326
326
|
depth=depth,
|
|
327
|
-
conditions=conditions,
|
|
327
|
+
# conditions=conditions,
|
|
328
|
+
# conditions=search_conditions.conditional,
|
|
329
|
+
# preexisting_conditions=search_conditions.conditional,
|
|
328
330
|
# node_joins=[]
|
|
329
331
|
)
|
|
330
332
|
|
|
@@ -341,6 +343,8 @@ def gen_merge_node(
|
|
|
341
343
|
search_conditions: WhereClause | None = None,
|
|
342
344
|
) -> Optional[MergeNode]:
|
|
343
345
|
|
|
346
|
+
if search_conditions:
|
|
347
|
+
all_concepts = unique(all_concepts + search_conditions.row_arguments, "address")
|
|
344
348
|
for filter_downstream in [True, False]:
|
|
345
349
|
weak_resolve = resolve_weak_components(
|
|
346
350
|
all_concepts,
|
|
@@ -5,16 +5,15 @@ from trilogy.core.models import (
|
|
|
5
5
|
RowsetDerivationStatement,
|
|
6
6
|
RowsetItem,
|
|
7
7
|
MultiSelectStatement,
|
|
8
|
+
WhereClause,
|
|
8
9
|
)
|
|
9
10
|
from trilogy.core.processing.nodes import MergeNode, History, StrategyNode
|
|
10
|
-
from trilogy.core.processing.nodes.base_node import concept_list_to_grain
|
|
11
11
|
from typing import List
|
|
12
12
|
|
|
13
13
|
from trilogy.core.enums import PurposeLineage
|
|
14
14
|
from trilogy.constants import logger
|
|
15
|
-
from trilogy.core.processing.utility import padding
|
|
16
|
-
from trilogy.core.processing.
|
|
17
|
-
|
|
15
|
+
from trilogy.core.processing.utility import padding, concept_to_relevant_joins
|
|
16
|
+
from trilogy.core.processing.nodes.base_node import concept_list_to_grain
|
|
18
17
|
|
|
19
18
|
LOGGER_PREFIX = "[GEN_ROWSET_NODE]"
|
|
20
19
|
|
|
@@ -27,6 +26,7 @@ def gen_rowset_node(
|
|
|
27
26
|
depth: int,
|
|
28
27
|
source_concepts,
|
|
29
28
|
history: History | None = None,
|
|
29
|
+
conditions: WhereClause | None = None,
|
|
30
30
|
) -> StrategyNode | None:
|
|
31
31
|
from trilogy.core.query_processor import get_query_node
|
|
32
32
|
|
|
@@ -44,7 +44,6 @@ def gen_rowset_node(
|
|
|
44
44
|
f"{padding(depth)}{LOGGER_PREFIX} Cannot generate parent rowset node for {concept}"
|
|
45
45
|
)
|
|
46
46
|
return None
|
|
47
|
-
|
|
48
47
|
enrichment = set([x.address for x in local_optional])
|
|
49
48
|
rowset_relevant = [x for x in rowset.derived_concepts]
|
|
50
49
|
select_hidden = set([x.address for x in select.hidden_components])
|
|
@@ -67,11 +66,10 @@ def gen_rowset_node(
|
|
|
67
66
|
final_hidden = rowset_hidden + [
|
|
68
67
|
x
|
|
69
68
|
for x in node.output_concepts
|
|
70
|
-
if x.address not in
|
|
69
|
+
if x.address not in local_optional + [concept]
|
|
71
70
|
and x.derivation != PurposeLineage.ROWSET
|
|
72
71
|
]
|
|
73
72
|
node.hide_output_concepts(final_hidden)
|
|
74
|
-
|
|
75
73
|
assert node.resolution_cache
|
|
76
74
|
# assume grain to be output of select
|
|
77
75
|
# but don't include anything hidden(the non-rowset concepts)
|
|
@@ -81,9 +79,7 @@ def gen_rowset_node(
|
|
|
81
79
|
for x in node.output_concepts
|
|
82
80
|
if x.address
|
|
83
81
|
not in [
|
|
84
|
-
y.
|
|
85
|
-
for y in node.hidden_concepts
|
|
86
|
-
if y.derivation != PurposeLineage.ROWSET
|
|
82
|
+
y for y in node.hidden_concepts if y.derivation != PurposeLineage.ROWSET
|
|
87
83
|
]
|
|
88
84
|
],
|
|
89
85
|
parent_sources=node.resolution_cache.datasources,
|
|
@@ -91,14 +87,16 @@ def gen_rowset_node(
|
|
|
91
87
|
|
|
92
88
|
node.rebuild_cache()
|
|
93
89
|
|
|
94
|
-
possible_joins = concept_to_relevant_joins(additional_relevant)
|
|
95
90
|
if not local_optional or all(
|
|
96
|
-
x.address in
|
|
91
|
+
x.address in node.output_concepts for x in local_optional
|
|
97
92
|
):
|
|
98
93
|
logger.info(
|
|
99
94
|
f"{padding(depth)}{LOGGER_PREFIX} no enrichment required for rowset node as all optional found or no optional; exiting early."
|
|
100
95
|
)
|
|
96
|
+
# node.set_preexisting_conditions(conditions.conditional if conditions else None)
|
|
101
97
|
return node
|
|
98
|
+
|
|
99
|
+
possible_joins = concept_to_relevant_joins(additional_relevant)
|
|
102
100
|
if not possible_joins:
|
|
103
101
|
logger.info(
|
|
104
102
|
f"{padding(depth)}{LOGGER_PREFIX} no possible joins for rowset node to get {[x.address for x in local_optional]}; have {[x.address for x in node.output_concepts]}"
|
|
@@ -110,6 +108,7 @@ def gen_rowset_node(
|
|
|
110
108
|
environment=environment,
|
|
111
109
|
g=g,
|
|
112
110
|
depth=depth + 1,
|
|
111
|
+
conditions=conditions,
|
|
113
112
|
)
|
|
114
113
|
if not enrich_node:
|
|
115
114
|
logger.info(
|
|
@@ -127,4 +126,5 @@ def gen_rowset_node(
|
|
|
127
126
|
enrich_node,
|
|
128
127
|
],
|
|
129
128
|
partial_concepts=node.partial_concepts,
|
|
129
|
+
preexisting_conditions=conditions.conditional if conditions else None,
|
|
130
130
|
)
|