pytrilogy 0.3.142__cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.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.
- LICENSE.md +19 -0
- _preql_import_resolver/__init__.py +5 -0
- _preql_import_resolver/_preql_import_resolver.cpython-313-x86_64-linux-gnu.so +0 -0
- pytrilogy-0.3.142.dist-info/METADATA +555 -0
- pytrilogy-0.3.142.dist-info/RECORD +200 -0
- pytrilogy-0.3.142.dist-info/WHEEL +5 -0
- pytrilogy-0.3.142.dist-info/entry_points.txt +2 -0
- pytrilogy-0.3.142.dist-info/licenses/LICENSE.md +19 -0
- trilogy/__init__.py +16 -0
- trilogy/ai/README.md +10 -0
- trilogy/ai/__init__.py +19 -0
- trilogy/ai/constants.py +92 -0
- trilogy/ai/conversation.py +107 -0
- trilogy/ai/enums.py +7 -0
- trilogy/ai/execute.py +50 -0
- trilogy/ai/models.py +34 -0
- trilogy/ai/prompts.py +100 -0
- trilogy/ai/providers/__init__.py +0 -0
- trilogy/ai/providers/anthropic.py +106 -0
- trilogy/ai/providers/base.py +24 -0
- trilogy/ai/providers/google.py +146 -0
- trilogy/ai/providers/openai.py +89 -0
- trilogy/ai/providers/utils.py +68 -0
- trilogy/authoring/README.md +3 -0
- trilogy/authoring/__init__.py +148 -0
- trilogy/constants.py +113 -0
- trilogy/core/README.md +52 -0
- trilogy/core/__init__.py +0 -0
- trilogy/core/constants.py +6 -0
- trilogy/core/enums.py +443 -0
- trilogy/core/env_processor.py +120 -0
- trilogy/core/environment_helpers.py +320 -0
- trilogy/core/ergonomics.py +193 -0
- trilogy/core/exceptions.py +123 -0
- trilogy/core/functions.py +1227 -0
- trilogy/core/graph_models.py +139 -0
- trilogy/core/internal.py +85 -0
- trilogy/core/models/__init__.py +0 -0
- trilogy/core/models/author.py +2669 -0
- trilogy/core/models/build.py +2521 -0
- trilogy/core/models/build_environment.py +180 -0
- trilogy/core/models/core.py +501 -0
- trilogy/core/models/datasource.py +322 -0
- trilogy/core/models/environment.py +751 -0
- trilogy/core/models/execute.py +1177 -0
- trilogy/core/optimization.py +251 -0
- trilogy/core/optimizations/__init__.py +12 -0
- trilogy/core/optimizations/base_optimization.py +17 -0
- trilogy/core/optimizations/hide_unused_concept.py +47 -0
- trilogy/core/optimizations/inline_datasource.py +102 -0
- trilogy/core/optimizations/predicate_pushdown.py +245 -0
- trilogy/core/processing/README.md +94 -0
- trilogy/core/processing/READMEv2.md +121 -0
- trilogy/core/processing/VIRTUAL_UNNEST.md +30 -0
- trilogy/core/processing/__init__.py +0 -0
- trilogy/core/processing/concept_strategies_v3.py +508 -0
- trilogy/core/processing/constants.py +15 -0
- trilogy/core/processing/discovery_node_factory.py +451 -0
- trilogy/core/processing/discovery_utility.py +548 -0
- trilogy/core/processing/discovery_validation.py +167 -0
- trilogy/core/processing/graph_utils.py +43 -0
- trilogy/core/processing/node_generators/README.md +9 -0
- trilogy/core/processing/node_generators/__init__.py +31 -0
- trilogy/core/processing/node_generators/basic_node.py +160 -0
- trilogy/core/processing/node_generators/common.py +268 -0
- trilogy/core/processing/node_generators/constant_node.py +38 -0
- trilogy/core/processing/node_generators/filter_node.py +315 -0
- trilogy/core/processing/node_generators/group_node.py +213 -0
- trilogy/core/processing/node_generators/group_to_node.py +117 -0
- trilogy/core/processing/node_generators/multiselect_node.py +205 -0
- trilogy/core/processing/node_generators/node_merge_node.py +653 -0
- trilogy/core/processing/node_generators/recursive_node.py +88 -0
- trilogy/core/processing/node_generators/rowset_node.py +165 -0
- trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
- trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +261 -0
- trilogy/core/processing/node_generators/select_merge_node.py +748 -0
- trilogy/core/processing/node_generators/select_node.py +95 -0
- trilogy/core/processing/node_generators/synonym_node.py +98 -0
- trilogy/core/processing/node_generators/union_node.py +91 -0
- trilogy/core/processing/node_generators/unnest_node.py +182 -0
- trilogy/core/processing/node_generators/window_node.py +201 -0
- trilogy/core/processing/nodes/README.md +28 -0
- trilogy/core/processing/nodes/__init__.py +179 -0
- trilogy/core/processing/nodes/base_node.py +519 -0
- trilogy/core/processing/nodes/filter_node.py +75 -0
- trilogy/core/processing/nodes/group_node.py +194 -0
- trilogy/core/processing/nodes/merge_node.py +420 -0
- trilogy/core/processing/nodes/recursive_node.py +46 -0
- trilogy/core/processing/nodes/select_node_v2.py +242 -0
- trilogy/core/processing/nodes/union_node.py +53 -0
- trilogy/core/processing/nodes/unnest_node.py +62 -0
- trilogy/core/processing/nodes/window_node.py +56 -0
- trilogy/core/processing/utility.py +823 -0
- trilogy/core/query_processor.py +596 -0
- trilogy/core/statements/README.md +35 -0
- trilogy/core/statements/__init__.py +0 -0
- trilogy/core/statements/author.py +536 -0
- trilogy/core/statements/build.py +0 -0
- trilogy/core/statements/common.py +20 -0
- trilogy/core/statements/execute.py +155 -0
- trilogy/core/table_processor.py +66 -0
- trilogy/core/utility.py +8 -0
- trilogy/core/validation/README.md +46 -0
- trilogy/core/validation/__init__.py +0 -0
- trilogy/core/validation/common.py +161 -0
- trilogy/core/validation/concept.py +146 -0
- trilogy/core/validation/datasource.py +227 -0
- trilogy/core/validation/environment.py +73 -0
- trilogy/core/validation/fix.py +256 -0
- trilogy/dialect/__init__.py +32 -0
- trilogy/dialect/base.py +1392 -0
- trilogy/dialect/bigquery.py +308 -0
- trilogy/dialect/common.py +147 -0
- trilogy/dialect/config.py +144 -0
- trilogy/dialect/dataframe.py +50 -0
- trilogy/dialect/duckdb.py +231 -0
- trilogy/dialect/enums.py +147 -0
- trilogy/dialect/metadata.py +173 -0
- trilogy/dialect/mock.py +190 -0
- trilogy/dialect/postgres.py +117 -0
- trilogy/dialect/presto.py +110 -0
- trilogy/dialect/results.py +89 -0
- trilogy/dialect/snowflake.py +129 -0
- trilogy/dialect/sql_server.py +137 -0
- trilogy/engine.py +48 -0
- trilogy/execution/config.py +75 -0
- trilogy/executor.py +568 -0
- trilogy/hooks/__init__.py +4 -0
- trilogy/hooks/base_hook.py +40 -0
- trilogy/hooks/graph_hook.py +139 -0
- trilogy/hooks/query_debugger.py +166 -0
- trilogy/metadata/__init__.py +0 -0
- trilogy/parser.py +10 -0
- trilogy/parsing/README.md +21 -0
- trilogy/parsing/__init__.py +0 -0
- trilogy/parsing/common.py +1069 -0
- trilogy/parsing/config.py +5 -0
- trilogy/parsing/exceptions.py +8 -0
- trilogy/parsing/helpers.py +1 -0
- trilogy/parsing/parse_engine.py +2813 -0
- trilogy/parsing/render.py +769 -0
- trilogy/parsing/trilogy.lark +540 -0
- trilogy/py.typed +0 -0
- trilogy/render.py +42 -0
- trilogy/scripts/README.md +9 -0
- trilogy/scripts/__init__.py +0 -0
- trilogy/scripts/agent.py +41 -0
- trilogy/scripts/agent_info.py +303 -0
- trilogy/scripts/common.py +355 -0
- trilogy/scripts/dependency/Cargo.lock +617 -0
- trilogy/scripts/dependency/Cargo.toml +39 -0
- trilogy/scripts/dependency/README.md +131 -0
- trilogy/scripts/dependency/build.sh +25 -0
- trilogy/scripts/dependency/src/directory_resolver.rs +177 -0
- trilogy/scripts/dependency/src/lib.rs +16 -0
- trilogy/scripts/dependency/src/main.rs +770 -0
- trilogy/scripts/dependency/src/parser.rs +435 -0
- trilogy/scripts/dependency/src/preql.pest +208 -0
- trilogy/scripts/dependency/src/python_bindings.rs +303 -0
- trilogy/scripts/dependency/src/resolver.rs +716 -0
- trilogy/scripts/dependency/tests/base.preql +3 -0
- trilogy/scripts/dependency/tests/cli_integration.rs +377 -0
- trilogy/scripts/dependency/tests/customer.preql +6 -0
- trilogy/scripts/dependency/tests/main.preql +9 -0
- trilogy/scripts/dependency/tests/orders.preql +7 -0
- trilogy/scripts/dependency/tests/test_data/base.preql +9 -0
- trilogy/scripts/dependency/tests/test_data/consumer.preql +1 -0
- trilogy/scripts/dependency.py +323 -0
- trilogy/scripts/display.py +512 -0
- trilogy/scripts/environment.py +46 -0
- trilogy/scripts/fmt.py +32 -0
- trilogy/scripts/ingest.py +471 -0
- trilogy/scripts/ingest_helpers/__init__.py +1 -0
- trilogy/scripts/ingest_helpers/foreign_keys.py +123 -0
- trilogy/scripts/ingest_helpers/formatting.py +93 -0
- trilogy/scripts/ingest_helpers/typing.py +161 -0
- trilogy/scripts/init.py +105 -0
- trilogy/scripts/parallel_execution.py +713 -0
- trilogy/scripts/plan.py +189 -0
- trilogy/scripts/run.py +63 -0
- trilogy/scripts/serve.py +140 -0
- trilogy/scripts/serve_helpers/__init__.py +41 -0
- trilogy/scripts/serve_helpers/file_discovery.py +142 -0
- trilogy/scripts/serve_helpers/index_generation.py +206 -0
- trilogy/scripts/serve_helpers/models.py +38 -0
- trilogy/scripts/single_execution.py +131 -0
- trilogy/scripts/testing.py +119 -0
- trilogy/scripts/trilogy.py +68 -0
- trilogy/std/__init__.py +0 -0
- trilogy/std/color.preql +3 -0
- trilogy/std/date.preql +13 -0
- trilogy/std/display.preql +18 -0
- trilogy/std/geography.preql +22 -0
- trilogy/std/metric.preql +15 -0
- trilogy/std/money.preql +67 -0
- trilogy/std/net.preql +14 -0
- trilogy/std/ranking.preql +7 -0
- trilogy/std/report.preql +5 -0
- trilogy/std/semantic.preql +6 -0
- trilogy/utility.py +34 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from trilogy.core.models.build import BuildConcept, BuildWhereClause
|
|
4
|
+
from trilogy.core.models.build_environment import BuildEnvironment
|
|
5
|
+
from trilogy.core.processing.nodes import History, StrategyNode
|
|
6
|
+
|
|
7
|
+
LOGGER_PREFIX = "[GEN_CONSTANT_NODE]"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def gen_constant_node(
|
|
11
|
+
concept: BuildConcept,
|
|
12
|
+
local_optional: List[BuildConcept],
|
|
13
|
+
environment: BuildEnvironment,
|
|
14
|
+
g,
|
|
15
|
+
depth: int,
|
|
16
|
+
source_concepts,
|
|
17
|
+
history: History | None = None,
|
|
18
|
+
conditions: BuildWhereClause | None = None,
|
|
19
|
+
accept_partial: bool = False,
|
|
20
|
+
):
|
|
21
|
+
"""our only goal here is to generate a row if conditions exist, or none if they do not"""
|
|
22
|
+
|
|
23
|
+
targets = [concept] + local_optional
|
|
24
|
+
if conditions:
|
|
25
|
+
targets += conditions.row_arguments
|
|
26
|
+
parent_node: StrategyNode | None = source_concepts(
|
|
27
|
+
mandatory_list=targets,
|
|
28
|
+
environment=environment,
|
|
29
|
+
g=g,
|
|
30
|
+
depth=depth + 1,
|
|
31
|
+
history=history,
|
|
32
|
+
conditions=conditions,
|
|
33
|
+
accept_partial=accept_partial,
|
|
34
|
+
)
|
|
35
|
+
if not parent_node:
|
|
36
|
+
return None
|
|
37
|
+
parent_node.set_output_concepts([concept] + local_optional)
|
|
38
|
+
return parent_node
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from trilogy.constants import logger
|
|
4
|
+
from trilogy.core.models.build import (
|
|
5
|
+
BuildConcept,
|
|
6
|
+
BuildFilterItem,
|
|
7
|
+
BuildWhereClause,
|
|
8
|
+
)
|
|
9
|
+
from trilogy.core.models.build_environment import BuildEnvironment
|
|
10
|
+
from trilogy.core.processing.node_generators.common import (
|
|
11
|
+
resolve_filter_parent_concepts,
|
|
12
|
+
)
|
|
13
|
+
from trilogy.core.processing.nodes import (
|
|
14
|
+
FilterNode,
|
|
15
|
+
History,
|
|
16
|
+
MergeNode,
|
|
17
|
+
SelectNode,
|
|
18
|
+
StrategyNode,
|
|
19
|
+
)
|
|
20
|
+
from trilogy.core.processing.utility import is_scalar_condition, padding, unique
|
|
21
|
+
|
|
22
|
+
LOGGER_PREFIX = "[GEN_FILTER_NODE]"
|
|
23
|
+
|
|
24
|
+
FILTER_TYPES = (BuildFilterItem,)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def pushdown_filter_to_parent(
|
|
28
|
+
local_optional: List[BuildConcept],
|
|
29
|
+
conditions: BuildWhereClause | None,
|
|
30
|
+
filter_where: BuildWhereClause,
|
|
31
|
+
same_filter_optional: list[BuildConcept],
|
|
32
|
+
depth: int,
|
|
33
|
+
) -> bool:
|
|
34
|
+
optimized_pushdown = False
|
|
35
|
+
if not is_scalar_condition(filter_where.conditional):
|
|
36
|
+
optimized_pushdown = False
|
|
37
|
+
elif not local_optional:
|
|
38
|
+
optimized_pushdown = True
|
|
39
|
+
elif conditions and conditions == filter_where:
|
|
40
|
+
logger.info(
|
|
41
|
+
f"{padding(depth)}{LOGGER_PREFIX} query conditions are the same as filter conditions, can optimize across all concepts"
|
|
42
|
+
)
|
|
43
|
+
optimized_pushdown = True
|
|
44
|
+
elif same_filter_optional == local_optional:
|
|
45
|
+
logger.info(
|
|
46
|
+
f"{padding(depth)}{LOGGER_PREFIX} all optional concepts are included in the filter, can optimize across all concepts"
|
|
47
|
+
)
|
|
48
|
+
optimized_pushdown = True
|
|
49
|
+
|
|
50
|
+
return optimized_pushdown
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def build_parent_concepts(
|
|
54
|
+
concept: BuildConcept,
|
|
55
|
+
environment: BuildEnvironment,
|
|
56
|
+
local_optional: List[BuildConcept],
|
|
57
|
+
conditions: BuildWhereClause | None = None,
|
|
58
|
+
depth: int = 0,
|
|
59
|
+
) -> tuple[
|
|
60
|
+
list[BuildConcept],
|
|
61
|
+
list[tuple[BuildConcept, ...]],
|
|
62
|
+
list[BuildConcept],
|
|
63
|
+
bool,
|
|
64
|
+
bool,
|
|
65
|
+
]:
|
|
66
|
+
parent_row_concepts, parent_existence_concepts = resolve_filter_parent_concepts(
|
|
67
|
+
concept, environment
|
|
68
|
+
)
|
|
69
|
+
if not isinstance(concept.lineage, FILTER_TYPES):
|
|
70
|
+
raise SyntaxError('Filter node must have a filter type lineage"')
|
|
71
|
+
filter_where = concept.lineage.where
|
|
72
|
+
|
|
73
|
+
same_filter_optional: list[BuildConcept] = []
|
|
74
|
+
# mypy struggled here? we shouldn't need explicit bools
|
|
75
|
+
global_filter_is_local_filter: bool = (
|
|
76
|
+
True if (conditions and conditions == filter_where) else False
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
exact_partial_matches = True
|
|
80
|
+
for x in local_optional:
|
|
81
|
+
if isinstance(x.lineage, FILTER_TYPES):
|
|
82
|
+
if set([x.address for x in x.lineage.where.concept_arguments]) == set(
|
|
83
|
+
[x.address for x in filter_where.concept_arguments]
|
|
84
|
+
):
|
|
85
|
+
exact_partial_matches = (
|
|
86
|
+
exact_partial_matches and x.lineage.where == filter_where
|
|
87
|
+
)
|
|
88
|
+
logger.info(
|
|
89
|
+
f"{padding(depth)}{LOGGER_PREFIX} fetching parents for peer {x.address} (of {concept.address})"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
for arg in x.lineage.content_concept_arguments:
|
|
93
|
+
if arg.address not in parent_row_concepts:
|
|
94
|
+
parent_row_concepts.append(arg)
|
|
95
|
+
same_filter_optional.append(x)
|
|
96
|
+
continue
|
|
97
|
+
elif global_filter_is_local_filter:
|
|
98
|
+
same_filter_optional.append(x)
|
|
99
|
+
# also append it to the parent row concepts
|
|
100
|
+
parent_row_concepts.append(x)
|
|
101
|
+
|
|
102
|
+
# sometimes, it's okay to include other local optional above the filter
|
|
103
|
+
# in case it is, prep our list
|
|
104
|
+
extra_row_level_optional: list[BuildConcept] = []
|
|
105
|
+
|
|
106
|
+
for x in local_optional:
|
|
107
|
+
if x.address in same_filter_optional:
|
|
108
|
+
continue
|
|
109
|
+
extra_row_level_optional.append(x)
|
|
110
|
+
is_optimized_pushdown = exact_partial_matches and pushdown_filter_to_parent(
|
|
111
|
+
local_optional, conditions, filter_where, same_filter_optional, depth
|
|
112
|
+
)
|
|
113
|
+
if not is_optimized_pushdown:
|
|
114
|
+
parent_row_concepts += extra_row_level_optional
|
|
115
|
+
return (
|
|
116
|
+
parent_row_concepts,
|
|
117
|
+
parent_existence_concepts,
|
|
118
|
+
same_filter_optional,
|
|
119
|
+
is_optimized_pushdown,
|
|
120
|
+
global_filter_is_local_filter,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def add_existence_sources(
|
|
125
|
+
core_parent_nodes: list[StrategyNode],
|
|
126
|
+
parent_existence_concepts: list[tuple[BuildConcept, ...]],
|
|
127
|
+
source_concepts,
|
|
128
|
+
environment,
|
|
129
|
+
g,
|
|
130
|
+
depth,
|
|
131
|
+
history,
|
|
132
|
+
):
|
|
133
|
+
for existence_tuple in parent_existence_concepts:
|
|
134
|
+
if not existence_tuple:
|
|
135
|
+
continue
|
|
136
|
+
logger.info(
|
|
137
|
+
f"{padding(depth)}{LOGGER_PREFIX} fetching filter node existence parents {[x.address for x in existence_tuple]}"
|
|
138
|
+
)
|
|
139
|
+
parent_existence = source_concepts(
|
|
140
|
+
mandatory_list=list(existence_tuple),
|
|
141
|
+
environment=environment,
|
|
142
|
+
g=g,
|
|
143
|
+
depth=depth + 1,
|
|
144
|
+
history=history,
|
|
145
|
+
)
|
|
146
|
+
if not parent_existence:
|
|
147
|
+
logger.info(
|
|
148
|
+
f"{padding(depth)}{LOGGER_PREFIX} filter existence node parents could not be found"
|
|
149
|
+
)
|
|
150
|
+
return None
|
|
151
|
+
core_parent_nodes.append(parent_existence)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def gen_filter_node(
|
|
155
|
+
concept: BuildConcept,
|
|
156
|
+
local_optional: List[BuildConcept],
|
|
157
|
+
environment: BuildEnvironment,
|
|
158
|
+
g,
|
|
159
|
+
depth: int,
|
|
160
|
+
source_concepts,
|
|
161
|
+
history: History | None = None,
|
|
162
|
+
conditions: BuildWhereClause | None = None,
|
|
163
|
+
) -> StrategyNode | None:
|
|
164
|
+
if not isinstance(concept.lineage, FILTER_TYPES):
|
|
165
|
+
raise SyntaxError('Filter node must have a filter type lineage"')
|
|
166
|
+
where = concept.lineage.where
|
|
167
|
+
|
|
168
|
+
(
|
|
169
|
+
parent_row_concepts,
|
|
170
|
+
parent_existence_concepts,
|
|
171
|
+
same_filter_optional,
|
|
172
|
+
optimized_pushdown,
|
|
173
|
+
global_filter_is_local_filter,
|
|
174
|
+
) = build_parent_concepts(
|
|
175
|
+
concept,
|
|
176
|
+
environment=environment,
|
|
177
|
+
local_optional=local_optional,
|
|
178
|
+
conditions=conditions,
|
|
179
|
+
depth=depth,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
row_parent: StrategyNode = source_concepts(
|
|
183
|
+
mandatory_list=parent_row_concepts,
|
|
184
|
+
environment=environment,
|
|
185
|
+
g=g,
|
|
186
|
+
depth=depth + 1,
|
|
187
|
+
history=history,
|
|
188
|
+
conditions=conditions,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
core_parent_nodes: list[StrategyNode] = []
|
|
192
|
+
flattened_existence = [x for y in parent_existence_concepts for x in y]
|
|
193
|
+
if parent_existence_concepts:
|
|
194
|
+
add_existence_sources(
|
|
195
|
+
core_parent_nodes,
|
|
196
|
+
parent_existence_concepts,
|
|
197
|
+
source_concepts,
|
|
198
|
+
environment,
|
|
199
|
+
g,
|
|
200
|
+
depth,
|
|
201
|
+
history,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if not row_parent:
|
|
205
|
+
logger.info(
|
|
206
|
+
f"{padding(depth)}{LOGGER_PREFIX} filter node row parents {[x.address for x in parent_row_concepts]} could not be found"
|
|
207
|
+
)
|
|
208
|
+
return None
|
|
209
|
+
else:
|
|
210
|
+
logger.info(
|
|
211
|
+
f"{padding(depth)}{LOGGER_PREFIX} filter node has row parents {[x.address for x in parent_row_concepts]} from node with output [{[x.address for x in row_parent.output_concepts]}] partial {row_parent.partial_concepts}"
|
|
212
|
+
)
|
|
213
|
+
if global_filter_is_local_filter:
|
|
214
|
+
logger.info(
|
|
215
|
+
f"{padding(depth)}{LOGGER_PREFIX} filter node conditions match global conditions adding row parent {row_parent.output_concepts} with condition {where.conditional}"
|
|
216
|
+
)
|
|
217
|
+
row_parent.add_parents(core_parent_nodes)
|
|
218
|
+
# all local optional will be in the parent already, so we can set outputs
|
|
219
|
+
row_parent.set_output_concepts([concept] + local_optional)
|
|
220
|
+
return row_parent
|
|
221
|
+
if optimized_pushdown:
|
|
222
|
+
logger.info(
|
|
223
|
+
f"{padding(depth)}{LOGGER_PREFIX} returning optimized filter node with pushdown to parent with condition {where.conditional} across {[concept] + same_filter_optional + row_parent.output_concepts} "
|
|
224
|
+
)
|
|
225
|
+
if isinstance(row_parent, SelectNode):
|
|
226
|
+
logger.info(
|
|
227
|
+
f"{padding(depth)}{LOGGER_PREFIX} nesting select node in strategy node"
|
|
228
|
+
)
|
|
229
|
+
parent = StrategyNode(
|
|
230
|
+
input_concepts=row_parent.output_concepts,
|
|
231
|
+
output_concepts=[concept]
|
|
232
|
+
+ same_filter_optional
|
|
233
|
+
+ row_parent.output_concepts,
|
|
234
|
+
environment=row_parent.environment,
|
|
235
|
+
parents=[row_parent],
|
|
236
|
+
depth=row_parent.depth,
|
|
237
|
+
partial_concepts=row_parent.partial_concepts,
|
|
238
|
+
force_group=False,
|
|
239
|
+
)
|
|
240
|
+
else:
|
|
241
|
+
parent = row_parent
|
|
242
|
+
parent.add_output_concepts([concept] + same_filter_optional)
|
|
243
|
+
parent.add_parents(core_parent_nodes)
|
|
244
|
+
if not parent.preexisting_conditions == where.conditional:
|
|
245
|
+
parent.add_condition(where.conditional)
|
|
246
|
+
parent.add_existence_concepts(flattened_existence, False)
|
|
247
|
+
# parent.grain = BuildGrain.from_concepts(
|
|
248
|
+
# parent.output_concepts,
|
|
249
|
+
# environment=environment,
|
|
250
|
+
# )
|
|
251
|
+
parent.rebuild_cache()
|
|
252
|
+
filter_node = parent
|
|
253
|
+
else:
|
|
254
|
+
core_parent_nodes.append(row_parent)
|
|
255
|
+
filter_node = FilterNode(
|
|
256
|
+
input_concepts=unique(
|
|
257
|
+
parent_row_concepts + flattened_existence,
|
|
258
|
+
"address",
|
|
259
|
+
),
|
|
260
|
+
output_concepts=[concept] + same_filter_optional + parent_row_concepts,
|
|
261
|
+
environment=environment,
|
|
262
|
+
parents=core_parent_nodes,
|
|
263
|
+
preexisting_conditions=conditions.conditional if conditions else None,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
if not local_optional or all(
|
|
267
|
+
[x.address in filter_node.output_concepts for x in local_optional]
|
|
268
|
+
):
|
|
269
|
+
optional_outputs = [
|
|
270
|
+
x for x in filter_node.output_concepts if x.address in local_optional
|
|
271
|
+
]
|
|
272
|
+
logger.info(
|
|
273
|
+
f"{padding(depth)}{LOGGER_PREFIX} no extra enrichment needed for filter node, has all of {[x.address for x in local_optional]}"
|
|
274
|
+
)
|
|
275
|
+
filter_node.set_output_concepts(
|
|
276
|
+
[
|
|
277
|
+
concept,
|
|
278
|
+
]
|
|
279
|
+
+ optional_outputs
|
|
280
|
+
)
|
|
281
|
+
return filter_node
|
|
282
|
+
logger.info(
|
|
283
|
+
f"{padding(depth)}{LOGGER_PREFIX} need to enrich filter node with additional concepts {[x.address for x in local_optional if x.address not in filter_node.output_concepts]}"
|
|
284
|
+
)
|
|
285
|
+
enrich_node: StrategyNode = source_concepts( # this fetches the parent + join keys
|
|
286
|
+
# to then connect to the rest of the query
|
|
287
|
+
mandatory_list=parent_row_concepts
|
|
288
|
+
+ [x for x in local_optional if x.address not in filter_node.output_concepts],
|
|
289
|
+
environment=environment,
|
|
290
|
+
g=g,
|
|
291
|
+
depth=depth + 1,
|
|
292
|
+
history=history,
|
|
293
|
+
conditions=conditions,
|
|
294
|
+
)
|
|
295
|
+
if not enrich_node:
|
|
296
|
+
logger.error(
|
|
297
|
+
f"{padding(depth)}{LOGGER_PREFIX} filter node enrichment node could not be found"
|
|
298
|
+
)
|
|
299
|
+
return filter_node
|
|
300
|
+
logger.info(
|
|
301
|
+
f"{padding(depth)}{LOGGER_PREFIX} returning filter node and enrich node with {enrich_node.output_concepts} and {enrich_node.input_concepts}"
|
|
302
|
+
)
|
|
303
|
+
return MergeNode(
|
|
304
|
+
input_concepts=filter_node.output_concepts + enrich_node.output_concepts,
|
|
305
|
+
output_concepts=[
|
|
306
|
+
concept,
|
|
307
|
+
]
|
|
308
|
+
+ local_optional,
|
|
309
|
+
environment=environment,
|
|
310
|
+
parents=[
|
|
311
|
+
filter_node,
|
|
312
|
+
enrich_node,
|
|
313
|
+
],
|
|
314
|
+
preexisting_conditions=conditions.conditional if conditions else None,
|
|
315
|
+
)
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from trilogy.constants import logger
|
|
4
|
+
from trilogy.core.internal import ALL_ROWS_CONCEPT
|
|
5
|
+
from trilogy.core.models.build import (
|
|
6
|
+
BuildAggregateWrapper,
|
|
7
|
+
BuildConcept,
|
|
8
|
+
BuildFunction,
|
|
9
|
+
BuildGrain,
|
|
10
|
+
BuildWhereClause,
|
|
11
|
+
LooseBuildConceptList,
|
|
12
|
+
)
|
|
13
|
+
from trilogy.core.models.build_environment import BuildEnvironment
|
|
14
|
+
from trilogy.core.processing.node_generators.common import (
|
|
15
|
+
gen_enrichment_node,
|
|
16
|
+
resolve_function_parent_concepts,
|
|
17
|
+
)
|
|
18
|
+
from trilogy.core.processing.nodes import GroupNode, History, StrategyNode
|
|
19
|
+
from trilogy.core.processing.utility import create_log_lambda, padding
|
|
20
|
+
from trilogy.utility import unique
|
|
21
|
+
|
|
22
|
+
LOGGER_PREFIX = "[GEN_GROUP_NODE]"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_aggregate_grain(
|
|
26
|
+
concept: BuildConcept, environment: BuildEnvironment
|
|
27
|
+
) -> BuildGrain:
|
|
28
|
+
parent_concepts: List[BuildConcept] = unique(
|
|
29
|
+
resolve_function_parent_concepts(concept, environment=environment), "address"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
if (
|
|
33
|
+
concept.grain
|
|
34
|
+
and len(concept.grain.components) > 0
|
|
35
|
+
and not concept.grain.abstract
|
|
36
|
+
):
|
|
37
|
+
grain_components = [environment.concepts[c] for c in concept.grain.components]
|
|
38
|
+
parent_concepts += grain_components
|
|
39
|
+
return BuildGrain.from_concepts(parent_concepts)
|
|
40
|
+
else:
|
|
41
|
+
return BuildGrain.from_concepts(parent_concepts)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def gen_group_node(
|
|
45
|
+
concept: BuildConcept,
|
|
46
|
+
local_optional: List[BuildConcept],
|
|
47
|
+
environment: BuildEnvironment,
|
|
48
|
+
g,
|
|
49
|
+
depth: int,
|
|
50
|
+
source_concepts,
|
|
51
|
+
history: History,
|
|
52
|
+
conditions: BuildWhereClause | None = None,
|
|
53
|
+
) -> StrategyNode | None:
|
|
54
|
+
# aggregates MUST always group to the proper grain
|
|
55
|
+
# except when the
|
|
56
|
+
parent_concepts: List[BuildConcept] = unique(
|
|
57
|
+
resolve_function_parent_concepts(concept, environment=environment), "address"
|
|
58
|
+
)
|
|
59
|
+
logger.info(
|
|
60
|
+
f"{padding(depth)}{LOGGER_PREFIX} parent concepts for {concept} {concept.lineage} are {[x.address for x in parent_concepts]} from group grain {concept.grain}"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# if the aggregation has a grain, we need to ensure these are the ONLY optional in the output of the select
|
|
64
|
+
output_concepts = [concept]
|
|
65
|
+
grain_components = [environment.concepts[c] for c in concept.grain.components]
|
|
66
|
+
if (
|
|
67
|
+
concept.grain
|
|
68
|
+
and len(concept.grain.components) > 0
|
|
69
|
+
and not concept.grain.abstract
|
|
70
|
+
):
|
|
71
|
+
|
|
72
|
+
parent_concepts += grain_components
|
|
73
|
+
build_grain_parents = get_aggregate_grain(concept, environment)
|
|
74
|
+
output_concepts += grain_components
|
|
75
|
+
for possible_agg in local_optional:
|
|
76
|
+
|
|
77
|
+
if not isinstance(
|
|
78
|
+
possible_agg.lineage,
|
|
79
|
+
(BuildAggregateWrapper, BuildFunction),
|
|
80
|
+
):
|
|
81
|
+
continue
|
|
82
|
+
if possible_agg.grain and possible_agg.grain != concept.grain:
|
|
83
|
+
logger.info(
|
|
84
|
+
f"{padding(depth)}{LOGGER_PREFIX} mismatched equivalent group by with grain {possible_agg.grain} for {concept.address}"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if possible_agg.grain and possible_agg.grain == concept.grain:
|
|
88
|
+
agg_parents: List[BuildConcept] = resolve_function_parent_concepts(
|
|
89
|
+
possible_agg,
|
|
90
|
+
environment=environment,
|
|
91
|
+
)
|
|
92
|
+
comp_grain = get_aggregate_grain(possible_agg, environment)
|
|
93
|
+
if set([x.address for x in agg_parents]).issubset(
|
|
94
|
+
set([x.address for x in parent_concepts])
|
|
95
|
+
):
|
|
96
|
+
output_concepts.append(possible_agg)
|
|
97
|
+
logger.info(
|
|
98
|
+
f"{padding(depth)}{LOGGER_PREFIX} found equivalent group by optional concept {possible_agg.address} for {concept.address}"
|
|
99
|
+
)
|
|
100
|
+
elif comp_grain == build_grain_parents:
|
|
101
|
+
extra = [x for x in agg_parents if x.address not in parent_concepts]
|
|
102
|
+
parent_concepts += extra
|
|
103
|
+
output_concepts.append(possible_agg)
|
|
104
|
+
logger.info(
|
|
105
|
+
f"{padding(depth)}{LOGGER_PREFIX} found equivalent group by optional concept {possible_agg.address} for {concept.address}"
|
|
106
|
+
)
|
|
107
|
+
else:
|
|
108
|
+
logger.info(
|
|
109
|
+
f"{padding(depth)}{LOGGER_PREFIX} cannot include optional agg {possible_agg.address}; it has mismatched parent grain {comp_grain } vs local parent {build_grain_parents}"
|
|
110
|
+
)
|
|
111
|
+
elif concept.grain.abstract:
|
|
112
|
+
for possible_agg in local_optional:
|
|
113
|
+
if not isinstance(
|
|
114
|
+
possible_agg.lineage,
|
|
115
|
+
(BuildAggregateWrapper, BuildFunction),
|
|
116
|
+
):
|
|
117
|
+
|
|
118
|
+
continue
|
|
119
|
+
logger.info(
|
|
120
|
+
f"{padding(depth)}{LOGGER_PREFIX} considering optional agg {possible_agg.address} for {concept.address}"
|
|
121
|
+
)
|
|
122
|
+
agg_parents = resolve_function_parent_concepts(
|
|
123
|
+
possible_agg,
|
|
124
|
+
environment=environment,
|
|
125
|
+
)
|
|
126
|
+
comp_grain = get_aggregate_grain(possible_agg, environment)
|
|
127
|
+
if not possible_agg.grain.abstract:
|
|
128
|
+
continue
|
|
129
|
+
if set([x.address for x in agg_parents]) == set(
|
|
130
|
+
[x.address for x in parent_concepts]
|
|
131
|
+
):
|
|
132
|
+
output_concepts.append(possible_agg)
|
|
133
|
+
logger.info(
|
|
134
|
+
f"{padding(depth)}{LOGGER_PREFIX} found equivalent group by optional concept {possible_agg.address} for {concept.address}"
|
|
135
|
+
)
|
|
136
|
+
elif comp_grain == get_aggregate_grain(concept, environment):
|
|
137
|
+
extra = [x for x in agg_parents if x.address not in parent_concepts]
|
|
138
|
+
parent_concepts += extra
|
|
139
|
+
output_concepts.append(possible_agg)
|
|
140
|
+
logger.info(
|
|
141
|
+
f"{padding(depth)}{LOGGER_PREFIX} found equivalent group by optional concept {possible_agg.address} for {concept.address}"
|
|
142
|
+
)
|
|
143
|
+
else:
|
|
144
|
+
logger.info(
|
|
145
|
+
f"{padding(depth)}{LOGGER_PREFIX} cannot include optional agg {possible_agg.address}; it has mismatched parent grain {comp_grain } vs local parent {get_aggregate_grain(concept, environment)}"
|
|
146
|
+
)
|
|
147
|
+
if parent_concepts:
|
|
148
|
+
target_grain = BuildGrain.from_concepts(parent_concepts)
|
|
149
|
+
logger.info(
|
|
150
|
+
f"{padding(depth)}{LOGGER_PREFIX} fetching group node parents {LooseBuildConceptList(concepts=parent_concepts)} with expected grain {target_grain}"
|
|
151
|
+
)
|
|
152
|
+
parent_concepts = unique(
|
|
153
|
+
[x for x in parent_concepts if not x.name == ALL_ROWS_CONCEPT], "address"
|
|
154
|
+
)
|
|
155
|
+
parent: StrategyNode | None = source_concepts(
|
|
156
|
+
mandatory_list=parent_concepts,
|
|
157
|
+
environment=environment,
|
|
158
|
+
g=g,
|
|
159
|
+
depth=depth,
|
|
160
|
+
history=history,
|
|
161
|
+
conditions=conditions,
|
|
162
|
+
)
|
|
163
|
+
if not parent:
|
|
164
|
+
logger.info(
|
|
165
|
+
f"{padding(depth)}{LOGGER_PREFIX} group by node parents unresolvable"
|
|
166
|
+
)
|
|
167
|
+
return None
|
|
168
|
+
parents: List[StrategyNode] = [parent]
|
|
169
|
+
else:
|
|
170
|
+
parents = []
|
|
171
|
+
|
|
172
|
+
group_node = GroupNode(
|
|
173
|
+
output_concepts=output_concepts,
|
|
174
|
+
input_concepts=parent_concepts,
|
|
175
|
+
environment=environment,
|
|
176
|
+
parents=parents,
|
|
177
|
+
depth=depth,
|
|
178
|
+
preexisting_conditions=conditions.conditional if conditions else None,
|
|
179
|
+
required_outputs=parent_concepts,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# early exit if no optional
|
|
183
|
+
|
|
184
|
+
if not local_optional:
|
|
185
|
+
logger.info(
|
|
186
|
+
f"{padding(depth)}{LOGGER_PREFIX} no optional concepts, returning group node"
|
|
187
|
+
)
|
|
188
|
+
return group_node
|
|
189
|
+
missing_optional = [
|
|
190
|
+
x.address for x in local_optional if x.address not in group_node.usable_outputs
|
|
191
|
+
]
|
|
192
|
+
if not missing_optional:
|
|
193
|
+
logger.info(
|
|
194
|
+
f"{padding(depth)}{LOGGER_PREFIX} no extra enrichment needed for group node, has all of {[x.address for x in local_optional]}"
|
|
195
|
+
)
|
|
196
|
+
return group_node
|
|
197
|
+
logger.info(
|
|
198
|
+
f"{padding(depth)}{LOGGER_PREFIX} group node for {concept.address} requires enrichment, missing {missing_optional}"
|
|
199
|
+
)
|
|
200
|
+
return gen_enrichment_node(
|
|
201
|
+
group_node,
|
|
202
|
+
join_keys=grain_components,
|
|
203
|
+
local_optional=local_optional,
|
|
204
|
+
environment=environment,
|
|
205
|
+
g=g,
|
|
206
|
+
depth=depth,
|
|
207
|
+
source_concepts=source_concepts,
|
|
208
|
+
log_lambda=create_log_lambda(
|
|
209
|
+
LOGGER_PREFIX + f" for {concept.address}", depth, logger
|
|
210
|
+
),
|
|
211
|
+
history=history,
|
|
212
|
+
conditions=conditions,
|
|
213
|
+
)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from trilogy.constants import logger
|
|
4
|
+
from trilogy.core.models.build import BuildConcept, BuildFunction, BuildWhereClause
|
|
5
|
+
from trilogy.core.models.build_environment import BuildEnvironment
|
|
6
|
+
|
|
7
|
+
# C:\Users\ethan\coding_projects\pytrilogy\trilogy\core\processing\node_generators\group_to_node.py
|
|
8
|
+
from trilogy.core.processing.nodes import (
|
|
9
|
+
GroupNode,
|
|
10
|
+
History,
|
|
11
|
+
MergeNode,
|
|
12
|
+
StrategyNode,
|
|
13
|
+
)
|
|
14
|
+
from trilogy.core.processing.utility import padding
|
|
15
|
+
|
|
16
|
+
LOGGER_PREFIX = "[GEN_GROUP_TO_NODE]"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def gen_group_to_node(
|
|
20
|
+
concept: BuildConcept,
|
|
21
|
+
local_optional,
|
|
22
|
+
environment: BuildEnvironment,
|
|
23
|
+
g,
|
|
24
|
+
depth: int,
|
|
25
|
+
source_concepts,
|
|
26
|
+
history: History | None = None,
|
|
27
|
+
conditions: BuildWhereClause | None = None,
|
|
28
|
+
) -> GroupNode | MergeNode:
|
|
29
|
+
# aggregates MUST always group to the proper grain
|
|
30
|
+
if not isinstance(concept.lineage, BuildFunction):
|
|
31
|
+
raise SyntaxError(
|
|
32
|
+
f"Group to should have function lineage, is {type(concept.lineage)}"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
parent_concepts: List[BuildConcept] = concept.lineage.concept_arguments
|
|
36
|
+
root = parent_concepts[0]
|
|
37
|
+
logger.info(
|
|
38
|
+
f"{padding(depth)}{LOGGER_PREFIX} group by node has required parents {[x.address for x in parent_concepts]}"
|
|
39
|
+
)
|
|
40
|
+
parents: List[StrategyNode] = [
|
|
41
|
+
source_concepts(
|
|
42
|
+
mandatory_list=parent_concepts,
|
|
43
|
+
environment=environment,
|
|
44
|
+
g=g,
|
|
45
|
+
depth=depth + 1,
|
|
46
|
+
history=history,
|
|
47
|
+
conditions=conditions,
|
|
48
|
+
)
|
|
49
|
+
]
|
|
50
|
+
outputs = parent_concepts + [concept]
|
|
51
|
+
group_node = GroupNode(
|
|
52
|
+
output_concepts=parent_concepts + [concept],
|
|
53
|
+
input_concepts=parent_concepts,
|
|
54
|
+
environment=environment,
|
|
55
|
+
parents=parents,
|
|
56
|
+
depth=depth,
|
|
57
|
+
preexisting_conditions=conditions.conditional if conditions else None,
|
|
58
|
+
hidden_concepts=set(
|
|
59
|
+
[
|
|
60
|
+
x.address
|
|
61
|
+
for x in outputs
|
|
62
|
+
if x.address not in local_optional
|
|
63
|
+
and x.address != concept.address
|
|
64
|
+
and x.address != root.address
|
|
65
|
+
]
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# early exit if no optional
|
|
70
|
+
missing_local_option: list[BuildConcept] = [
|
|
71
|
+
x for x in local_optional if x not in group_node.output_concepts
|
|
72
|
+
]
|
|
73
|
+
if not missing_local_option:
|
|
74
|
+
logger.info(
|
|
75
|
+
f"{padding(depth)}{LOGGER_PREFIX} no missing local optional required, returning group node only."
|
|
76
|
+
)
|
|
77
|
+
return group_node
|
|
78
|
+
|
|
79
|
+
logger.info(
|
|
80
|
+
f"{padding(depth)}{LOGGER_PREFIX} group by node is missing required optional {[x.address for x in missing_local_option]}"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# the keys we group by
|
|
84
|
+
# are what we can use for enrichment
|
|
85
|
+
enrich_node = source_concepts( # this fetches the parent + join keys
|
|
86
|
+
# to then connect to the rest of the query
|
|
87
|
+
mandatory_list=parent_concepts + local_optional,
|
|
88
|
+
environment=environment,
|
|
89
|
+
g=g,
|
|
90
|
+
depth=depth + 1,
|
|
91
|
+
history=history,
|
|
92
|
+
conditions=conditions,
|
|
93
|
+
)
|
|
94
|
+
if not enrich_node:
|
|
95
|
+
logger.info(
|
|
96
|
+
f"{padding(depth)}{LOGGER_PREFIX} group by node enrich node, returning group node only."
|
|
97
|
+
)
|
|
98
|
+
return group_node
|
|
99
|
+
logger.info(
|
|
100
|
+
f"{padding(depth)}{LOGGER_PREFIX} returning group to node with enrichment."
|
|
101
|
+
)
|
|
102
|
+
return MergeNode(
|
|
103
|
+
input_concepts=[concept]
|
|
104
|
+
+ local_optional
|
|
105
|
+
+ [x for x in parent_concepts if x.address != concept.address],
|
|
106
|
+
output_concepts=[concept] + local_optional,
|
|
107
|
+
environment=environment,
|
|
108
|
+
parents=[
|
|
109
|
+
# this node gets the group
|
|
110
|
+
group_node,
|
|
111
|
+
# this node gets enrichment
|
|
112
|
+
enrich_node,
|
|
113
|
+
],
|
|
114
|
+
whole_grain=True,
|
|
115
|
+
depth=depth,
|
|
116
|
+
preexisting_conditions=conditions.conditional if conditions else None,
|
|
117
|
+
)
|