pytrilogy 0.3.148__cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.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-312-aarch64-linux-gnu.so +0 -0
- pytrilogy-0.3.148.dist-info/METADATA +555 -0
- pytrilogy-0.3.148.dist-info/RECORD +206 -0
- pytrilogy-0.3.148.dist-info/WHEEL +5 -0
- pytrilogy-0.3.148.dist-info/entry_points.txt +2 -0
- pytrilogy-0.3.148.dist-info/licenses/LICENSE.md +19 -0
- trilogy/__init__.py +27 -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 +119 -0
- trilogy/core/README.md +52 -0
- trilogy/core/__init__.py +0 -0
- trilogy/core/constants.py +6 -0
- trilogy/core/enums.py +454 -0
- trilogy/core/env_processor.py +239 -0
- trilogy/core/environment_helpers.py +320 -0
- trilogy/core/ergonomics.py +193 -0
- trilogy/core/exceptions.py +123 -0
- trilogy/core/functions.py +1240 -0
- trilogy/core/graph_models.py +142 -0
- trilogy/core/internal.py +85 -0
- trilogy/core/models/__init__.py +0 -0
- trilogy/core/models/author.py +2662 -0
- trilogy/core/models/build.py +2603 -0
- trilogy/core/models/build_environment.py +165 -0
- trilogy/core/models/core.py +506 -0
- trilogy/core/models/datasource.py +434 -0
- trilogy/core/models/environment.py +756 -0
- trilogy/core/models/execute.py +1213 -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 +270 -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 +207 -0
- trilogy/core/processing/node_generators/node_merge_node.py +695 -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 +786 -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 +522 -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 +604 -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 +1431 -0
- trilogy/dialect/bigquery.py +314 -0
- trilogy/dialect/common.py +147 -0
- trilogy/dialect/config.py +159 -0
- trilogy/dialect/dataframe.py +50 -0
- trilogy/dialect/duckdb.py +376 -0
- trilogy/dialect/enums.py +149 -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/__init__.py +17 -0
- trilogy/execution/config.py +119 -0
- trilogy/execution/state/__init__.py +0 -0
- trilogy/execution/state/file_state_store.py +0 -0
- trilogy/execution/state/sqllite_state_store.py +0 -0
- trilogy/execution/state/state_store.py +301 -0
- trilogy/executor.py +656 -0
- trilogy/hooks/__init__.py +4 -0
- trilogy/hooks/base_hook.py +40 -0
- trilogy/hooks/graph_hook.py +135 -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 +2863 -0
- trilogy/parsing/render.py +773 -0
- trilogy/parsing/trilogy.lark +544 -0
- trilogy/py.typed +0 -0
- trilogy/render.py +45 -0
- trilogy/scripts/README.md +9 -0
- trilogy/scripts/__init__.py +0 -0
- trilogy/scripts/agent.py +41 -0
- trilogy/scripts/agent_info.py +306 -0
- trilogy/scripts/common.py +430 -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 +387 -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 +311 -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 +555 -0
- trilogy/scripts/environment.py +59 -0
- trilogy/scripts/fmt.py +32 -0
- trilogy/scripts/ingest.py +472 -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 +748 -0
- trilogy/scripts/plan.py +189 -0
- trilogy/scripts/refresh.py +106 -0
- trilogy/scripts/run.py +79 -0
- trilogy/scripts/serve.py +202 -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 +129 -0
- trilogy/scripts/trilogy.py +75 -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,451 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import List, Optional, Protocol, Union
|
|
3
|
+
|
|
4
|
+
from trilogy.constants import logger
|
|
5
|
+
from trilogy.core.enums import Derivation, Granularity
|
|
6
|
+
from trilogy.core.graph_models import ReferenceGraph
|
|
7
|
+
from trilogy.core.models.build import (
|
|
8
|
+
BuildConcept,
|
|
9
|
+
BuildWhereClause,
|
|
10
|
+
)
|
|
11
|
+
from trilogy.core.models.build_environment import BuildEnvironment
|
|
12
|
+
from trilogy.core.processing.discovery_utility import LOGGER_PREFIX, depth_to_prefix
|
|
13
|
+
from trilogy.core.processing.node_generators import (
|
|
14
|
+
gen_basic_node,
|
|
15
|
+
gen_constant_node,
|
|
16
|
+
gen_filter_node,
|
|
17
|
+
gen_group_node,
|
|
18
|
+
gen_group_to_node,
|
|
19
|
+
gen_merge_node,
|
|
20
|
+
gen_multiselect_node,
|
|
21
|
+
gen_recursive_node,
|
|
22
|
+
gen_rowset_node,
|
|
23
|
+
gen_synonym_node,
|
|
24
|
+
gen_union_node,
|
|
25
|
+
gen_unnest_node,
|
|
26
|
+
gen_window_node,
|
|
27
|
+
)
|
|
28
|
+
from trilogy.core.processing.nodes import (
|
|
29
|
+
History,
|
|
30
|
+
StrategyNode,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SearchConceptsType(Protocol):
|
|
35
|
+
def __call__(
|
|
36
|
+
self,
|
|
37
|
+
mandatory_list: List[BuildConcept],
|
|
38
|
+
history: History,
|
|
39
|
+
environment: BuildEnvironment,
|
|
40
|
+
depth: int,
|
|
41
|
+
g: ReferenceGraph,
|
|
42
|
+
accept_partial: bool = False,
|
|
43
|
+
conditions: Optional[BuildWhereClause] = None,
|
|
44
|
+
) -> Union[StrategyNode, None]: ...
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class NodeGenerationContext:
|
|
49
|
+
"""Encapsulates common parameters for node generation."""
|
|
50
|
+
|
|
51
|
+
concept: BuildConcept
|
|
52
|
+
local_optional: List[BuildConcept]
|
|
53
|
+
environment: BuildEnvironment
|
|
54
|
+
g: ReferenceGraph
|
|
55
|
+
depth: int
|
|
56
|
+
source_concepts: SearchConceptsType
|
|
57
|
+
history: History
|
|
58
|
+
accept_partial: bool = False
|
|
59
|
+
conditions: Optional[BuildWhereClause] = None
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def next_depth(self) -> int:
|
|
63
|
+
return self.depth + 1
|
|
64
|
+
|
|
65
|
+
def log_generation(self, node_type: str, extra_info: str = "") -> None:
|
|
66
|
+
"""Centralized logging for node generation."""
|
|
67
|
+
optional_addresses = [x.address for x in self.local_optional]
|
|
68
|
+
base_msg = f"for {self.concept.address}, generating {node_type} node with optional {optional_addresses}"
|
|
69
|
+
|
|
70
|
+
if extra_info:
|
|
71
|
+
base_msg += f" and {extra_info}"
|
|
72
|
+
|
|
73
|
+
logger.info(f"{depth_to_prefix(self.depth)}{LOGGER_PREFIX} {base_msg}")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def restrict_node_outputs_targets(
|
|
77
|
+
node: StrategyNode, targets: list[BuildConcept], depth: int
|
|
78
|
+
) -> list[BuildConcept]:
|
|
79
|
+
"""Restricts node outputs to target concepts and returns extra concepts."""
|
|
80
|
+
ex_resolve = node.resolve()
|
|
81
|
+
target_addresses = {y.address for y in targets}
|
|
82
|
+
|
|
83
|
+
extra = [
|
|
84
|
+
x
|
|
85
|
+
for x in ex_resolve.output_concepts
|
|
86
|
+
if x.address not in target_addresses
|
|
87
|
+
and not any(c in targets for c in x.pseudonyms)
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
base = [
|
|
91
|
+
x
|
|
92
|
+
for x in ex_resolve.output_concepts
|
|
93
|
+
if x.address not in {c.address for c in extra}
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
logger.info(
|
|
97
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} reducing final outputs, "
|
|
98
|
+
f"was {[c.address for c in ex_resolve.output_concepts]} "
|
|
99
|
+
f"with extra {[c.address for c in extra]}, remaining {base} (targets {target_addresses})"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Add missing targets
|
|
103
|
+
for target in targets:
|
|
104
|
+
if target.address not in {c.address for c in base}:
|
|
105
|
+
base.append(target)
|
|
106
|
+
|
|
107
|
+
node.set_output_concepts(base)
|
|
108
|
+
return extra
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# Simple factory functions for basic derivation types
|
|
112
|
+
def _generate_window_node(ctx: NodeGenerationContext) -> StrategyNode | None:
|
|
113
|
+
ctx.log_generation("window")
|
|
114
|
+
return gen_window_node(
|
|
115
|
+
ctx.concept,
|
|
116
|
+
ctx.local_optional,
|
|
117
|
+
history=ctx.history,
|
|
118
|
+
environment=ctx.environment,
|
|
119
|
+
g=ctx.g,
|
|
120
|
+
depth=ctx.next_depth,
|
|
121
|
+
source_concepts=ctx.source_concepts,
|
|
122
|
+
conditions=ctx.conditions,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _generate_filter_node(ctx: NodeGenerationContext) -> StrategyNode | None:
|
|
127
|
+
ctx.log_generation("filter")
|
|
128
|
+
return gen_filter_node(
|
|
129
|
+
ctx.concept,
|
|
130
|
+
ctx.local_optional,
|
|
131
|
+
history=ctx.history,
|
|
132
|
+
environment=ctx.environment,
|
|
133
|
+
g=ctx.g,
|
|
134
|
+
depth=ctx.next_depth,
|
|
135
|
+
source_concepts=ctx.source_concepts,
|
|
136
|
+
conditions=ctx.conditions,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _generate_unnest_node(ctx: NodeGenerationContext) -> StrategyNode | None:
|
|
141
|
+
ctx.log_generation("unnest", f"condition {ctx.conditions}")
|
|
142
|
+
return gen_unnest_node(
|
|
143
|
+
ctx.concept,
|
|
144
|
+
ctx.local_optional,
|
|
145
|
+
history=ctx.history,
|
|
146
|
+
environment=ctx.environment,
|
|
147
|
+
g=ctx.g,
|
|
148
|
+
depth=ctx.next_depth,
|
|
149
|
+
source_concepts=ctx.source_concepts,
|
|
150
|
+
conditions=ctx.conditions,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _generate_recursive_node(ctx: NodeGenerationContext) -> StrategyNode | None:
|
|
155
|
+
ctx.log_generation("recursive", f"condition {ctx.conditions}")
|
|
156
|
+
return gen_recursive_node(
|
|
157
|
+
ctx.concept,
|
|
158
|
+
ctx.local_optional,
|
|
159
|
+
history=ctx.history,
|
|
160
|
+
environment=ctx.environment,
|
|
161
|
+
g=ctx.g,
|
|
162
|
+
depth=ctx.next_depth,
|
|
163
|
+
source_concepts=ctx.source_concepts,
|
|
164
|
+
conditions=ctx.conditions,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _generate_union_node(ctx: NodeGenerationContext) -> StrategyNode | None:
|
|
169
|
+
ctx.log_generation("union", f"condition {ctx.conditions}")
|
|
170
|
+
return gen_union_node(
|
|
171
|
+
ctx.concept,
|
|
172
|
+
ctx.local_optional,
|
|
173
|
+
ctx.environment,
|
|
174
|
+
ctx.g,
|
|
175
|
+
ctx.next_depth,
|
|
176
|
+
ctx.source_concepts,
|
|
177
|
+
ctx.history,
|
|
178
|
+
conditions=ctx.conditions,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _generate_aggregate_node(ctx: NodeGenerationContext) -> StrategyNode | None:
|
|
183
|
+
# Filter out constants to avoid multiplication issues
|
|
184
|
+
agg_optional = [
|
|
185
|
+
x
|
|
186
|
+
for x in ctx.local_optional
|
|
187
|
+
if not (
|
|
188
|
+
x.granularity == Granularity.SINGLE_ROW
|
|
189
|
+
and x.derivation != Derivation.AGGREGATE
|
|
190
|
+
)
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
logger.info(
|
|
194
|
+
f"{depth_to_prefix(ctx.depth)}{LOGGER_PREFIX} "
|
|
195
|
+
f"for {ctx.concept.address}, generating aggregate node with optional {agg_optional}"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
return gen_group_node(
|
|
199
|
+
ctx.concept,
|
|
200
|
+
agg_optional,
|
|
201
|
+
history=ctx.history,
|
|
202
|
+
environment=ctx.environment,
|
|
203
|
+
g=ctx.g,
|
|
204
|
+
depth=ctx.next_depth,
|
|
205
|
+
source_concepts=ctx.source_concepts,
|
|
206
|
+
conditions=ctx.conditions,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _generate_rowset_node(ctx: NodeGenerationContext) -> StrategyNode | None:
|
|
211
|
+
ctx.log_generation("rowset")
|
|
212
|
+
return gen_rowset_node(
|
|
213
|
+
ctx.concept,
|
|
214
|
+
ctx.local_optional,
|
|
215
|
+
ctx.environment,
|
|
216
|
+
ctx.g,
|
|
217
|
+
ctx.next_depth,
|
|
218
|
+
ctx.source_concepts,
|
|
219
|
+
ctx.history,
|
|
220
|
+
conditions=ctx.conditions,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _generate_multiselect_node(ctx: NodeGenerationContext) -> StrategyNode | None:
|
|
225
|
+
ctx.log_generation("multiselect")
|
|
226
|
+
return gen_multiselect_node(
|
|
227
|
+
ctx.concept,
|
|
228
|
+
ctx.local_optional,
|
|
229
|
+
ctx.environment,
|
|
230
|
+
ctx.g,
|
|
231
|
+
ctx.next_depth,
|
|
232
|
+
ctx.source_concepts,
|
|
233
|
+
ctx.history,
|
|
234
|
+
conditions=ctx.conditions,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _generate_group_to_node(ctx: NodeGenerationContext) -> StrategyNode | None:
|
|
239
|
+
ctx.log_generation("group to grain")
|
|
240
|
+
return gen_group_to_node(
|
|
241
|
+
ctx.concept,
|
|
242
|
+
ctx.local_optional,
|
|
243
|
+
ctx.environment,
|
|
244
|
+
ctx.g,
|
|
245
|
+
ctx.next_depth,
|
|
246
|
+
ctx.source_concepts,
|
|
247
|
+
ctx.history,
|
|
248
|
+
conditions=ctx.conditions,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _generate_basic_node(ctx: NodeGenerationContext) -> StrategyNode | None:
|
|
253
|
+
ctx.log_generation("basic")
|
|
254
|
+
return gen_basic_node(
|
|
255
|
+
ctx.concept,
|
|
256
|
+
ctx.local_optional,
|
|
257
|
+
history=ctx.history,
|
|
258
|
+
environment=ctx.environment,
|
|
259
|
+
g=ctx.g,
|
|
260
|
+
depth=ctx.next_depth,
|
|
261
|
+
source_concepts=ctx.source_concepts,
|
|
262
|
+
conditions=ctx.conditions,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _generate_constant_node(ctx: NodeGenerationContext) -> StrategyNode | None:
|
|
267
|
+
ctx.log_generation("constant")
|
|
268
|
+
return gen_constant_node(
|
|
269
|
+
ctx.concept,
|
|
270
|
+
ctx.local_optional,
|
|
271
|
+
history=ctx.history,
|
|
272
|
+
environment=ctx.environment,
|
|
273
|
+
g=ctx.g,
|
|
274
|
+
depth=ctx.next_depth,
|
|
275
|
+
source_concepts=ctx.source_concepts,
|
|
276
|
+
conditions=ctx.conditions,
|
|
277
|
+
accept_partial=ctx.accept_partial,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
class RootNodeHandler:
|
|
282
|
+
"""Handles complex root node generation logic."""
|
|
283
|
+
|
|
284
|
+
def __init__(self, context: NodeGenerationContext):
|
|
285
|
+
self.ctx = context
|
|
286
|
+
|
|
287
|
+
def generate(self) -> Optional[StrategyNode]:
|
|
288
|
+
self.ctx.log_generation("select", "including condition inputs")
|
|
289
|
+
|
|
290
|
+
root_targets = [self.ctx.concept] + self.ctx.local_optional
|
|
291
|
+
|
|
292
|
+
return self._resolve_root_concepts(root_targets)
|
|
293
|
+
|
|
294
|
+
def _resolve_root_concepts(
|
|
295
|
+
self, root_targets: List[BuildConcept]
|
|
296
|
+
) -> Optional[StrategyNode]:
|
|
297
|
+
expanded_node = self._try_merge_expansion(root_targets)
|
|
298
|
+
if expanded_node:
|
|
299
|
+
return expanded_node
|
|
300
|
+
if self.ctx.accept_partial:
|
|
301
|
+
synonym_node = self._try_synonym_resolution(root_targets)
|
|
302
|
+
if synonym_node:
|
|
303
|
+
logger.info(
|
|
304
|
+
f"{depth_to_prefix(self.ctx.depth)}{LOGGER_PREFIX} "
|
|
305
|
+
f"resolved root concepts through synonyms"
|
|
306
|
+
)
|
|
307
|
+
return synonym_node
|
|
308
|
+
|
|
309
|
+
return None
|
|
310
|
+
|
|
311
|
+
def _try_merge_expansion(
|
|
312
|
+
self, root_targets: List[BuildConcept]
|
|
313
|
+
) -> Optional[StrategyNode]:
|
|
314
|
+
for accept_partial in [False, True]:
|
|
315
|
+
expanded = gen_merge_node(
|
|
316
|
+
all_concepts=root_targets,
|
|
317
|
+
environment=self.ctx.environment,
|
|
318
|
+
g=self.ctx.g,
|
|
319
|
+
depth=self.ctx.next_depth,
|
|
320
|
+
source_concepts=self.ctx.source_concepts,
|
|
321
|
+
history=self.ctx.history,
|
|
322
|
+
search_conditions=self.ctx.conditions,
|
|
323
|
+
accept_partial=accept_partial,
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
if expanded:
|
|
327
|
+
self._handle_expanded_node(expanded, root_targets)
|
|
328
|
+
return expanded
|
|
329
|
+
|
|
330
|
+
logger.info(
|
|
331
|
+
f"{depth_to_prefix(self.ctx.depth)}{LOGGER_PREFIX} "
|
|
332
|
+
f"could not find additional concept(s) to inject"
|
|
333
|
+
)
|
|
334
|
+
return None
|
|
335
|
+
|
|
336
|
+
def _handle_expanded_node(
|
|
337
|
+
self, expanded: StrategyNode, root_targets: List[BuildConcept]
|
|
338
|
+
) -> None:
|
|
339
|
+
extra = restrict_node_outputs_targets(expanded, root_targets, self.ctx.depth)
|
|
340
|
+
|
|
341
|
+
logger.info(
|
|
342
|
+
f"{depth_to_prefix(self.ctx.depth)}{LOGGER_PREFIX} "
|
|
343
|
+
f"Found connections for {[c.address for c in root_targets]} "
|
|
344
|
+
f"via concept addition; removing extra {[c.address for c in extra]}"
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
def _try_synonym_resolution(
|
|
348
|
+
self, root_targets: List[BuildConcept]
|
|
349
|
+
) -> Optional[StrategyNode]:
|
|
350
|
+
logger.info(
|
|
351
|
+
f"{depth_to_prefix(self.ctx.depth)}{LOGGER_PREFIX} "
|
|
352
|
+
f"Could not resolve root concepts, checking for synonyms for {root_targets}"
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
if not self.ctx.history.check_started(
|
|
356
|
+
root_targets,
|
|
357
|
+
accept_partial=self.ctx.accept_partial,
|
|
358
|
+
conditions=self.ctx.conditions,
|
|
359
|
+
):
|
|
360
|
+
self.ctx.history.log_start(
|
|
361
|
+
root_targets,
|
|
362
|
+
accept_partial=self.ctx.accept_partial,
|
|
363
|
+
conditions=self.ctx.conditions,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
resolved = gen_synonym_node(
|
|
367
|
+
all_concepts=root_targets,
|
|
368
|
+
environment=self.ctx.environment,
|
|
369
|
+
g=self.ctx.g,
|
|
370
|
+
depth=self.ctx.next_depth,
|
|
371
|
+
source_concepts=self.ctx.source_concepts,
|
|
372
|
+
history=self.ctx.history,
|
|
373
|
+
conditions=self.ctx.conditions,
|
|
374
|
+
accept_partial=self.ctx.accept_partial,
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
if resolved:
|
|
378
|
+
logger.info(
|
|
379
|
+
f"{depth_to_prefix(self.ctx.depth)}{LOGGER_PREFIX} "
|
|
380
|
+
f"resolved concepts through synonyms"
|
|
381
|
+
)
|
|
382
|
+
return resolved
|
|
383
|
+
else:
|
|
384
|
+
logger.info(
|
|
385
|
+
f"{depth_to_prefix(self.ctx.depth)}{LOGGER_PREFIX} "
|
|
386
|
+
f"skipping synonym search, already in a recursion for these concepts"
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
return None
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def generate_node(
|
|
393
|
+
concept: BuildConcept,
|
|
394
|
+
local_optional: List[BuildConcept],
|
|
395
|
+
environment: BuildEnvironment,
|
|
396
|
+
g: ReferenceGraph,
|
|
397
|
+
depth: int,
|
|
398
|
+
source_concepts: SearchConceptsType,
|
|
399
|
+
history: History,
|
|
400
|
+
accept_partial: bool,
|
|
401
|
+
conditions: BuildWhereClause | None = None,
|
|
402
|
+
) -> StrategyNode | None:
|
|
403
|
+
|
|
404
|
+
context = NodeGenerationContext(
|
|
405
|
+
concept=concept,
|
|
406
|
+
local_optional=local_optional,
|
|
407
|
+
environment=environment,
|
|
408
|
+
g=g,
|
|
409
|
+
depth=depth,
|
|
410
|
+
source_concepts=source_concepts,
|
|
411
|
+
history=history,
|
|
412
|
+
accept_partial=accept_partial,
|
|
413
|
+
conditions=conditions,
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
# Try materialized concept first
|
|
417
|
+
# this is worth checking every loop iteration
|
|
418
|
+
candidate = history.gen_select_node(
|
|
419
|
+
[concept] + local_optional,
|
|
420
|
+
environment,
|
|
421
|
+
g,
|
|
422
|
+
depth + 1,
|
|
423
|
+
fail_if_not_found=False,
|
|
424
|
+
accept_partial=accept_partial,
|
|
425
|
+
conditions=conditions,
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
if candidate:
|
|
429
|
+
return candidate
|
|
430
|
+
|
|
431
|
+
# Delegate to appropriate handler based on derivation
|
|
432
|
+
derivation_handlers = {
|
|
433
|
+
Derivation.WINDOW: lambda: _generate_window_node(context),
|
|
434
|
+
Derivation.FILTER: lambda: _generate_filter_node(context),
|
|
435
|
+
Derivation.UNNEST: lambda: _generate_unnest_node(context),
|
|
436
|
+
Derivation.RECURSIVE: lambda: _generate_recursive_node(context),
|
|
437
|
+
Derivation.UNION: lambda: _generate_union_node(context),
|
|
438
|
+
Derivation.AGGREGATE: lambda: _generate_aggregate_node(context),
|
|
439
|
+
Derivation.ROWSET: lambda: _generate_rowset_node(context),
|
|
440
|
+
Derivation.MULTISELECT: lambda: _generate_multiselect_node(context),
|
|
441
|
+
Derivation.GROUP_TO: lambda: _generate_group_to_node(context),
|
|
442
|
+
Derivation.BASIC: lambda: _generate_basic_node(context),
|
|
443
|
+
Derivation.ROOT: lambda: RootNodeHandler(context).generate(),
|
|
444
|
+
Derivation.CONSTANT: lambda: _generate_constant_node(context),
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
handler = derivation_handlers.get(concept.derivation)
|
|
448
|
+
if not handler:
|
|
449
|
+
raise ValueError(f"Unknown derivation {concept.derivation} on {concept}")
|
|
450
|
+
|
|
451
|
+
return handler()
|