pytrilogy 0.3.142__cp312-cp312-win_amd64.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.
Files changed (200) hide show
  1. LICENSE.md +19 -0
  2. _preql_import_resolver/__init__.py +5 -0
  3. _preql_import_resolver/_preql_import_resolver.cp312-win_amd64.pyd +0 -0
  4. pytrilogy-0.3.142.dist-info/METADATA +555 -0
  5. pytrilogy-0.3.142.dist-info/RECORD +200 -0
  6. pytrilogy-0.3.142.dist-info/WHEEL +4 -0
  7. pytrilogy-0.3.142.dist-info/entry_points.txt +2 -0
  8. pytrilogy-0.3.142.dist-info/licenses/LICENSE.md +19 -0
  9. trilogy/__init__.py +16 -0
  10. trilogy/ai/README.md +10 -0
  11. trilogy/ai/__init__.py +19 -0
  12. trilogy/ai/constants.py +92 -0
  13. trilogy/ai/conversation.py +107 -0
  14. trilogy/ai/enums.py +7 -0
  15. trilogy/ai/execute.py +50 -0
  16. trilogy/ai/models.py +34 -0
  17. trilogy/ai/prompts.py +100 -0
  18. trilogy/ai/providers/__init__.py +0 -0
  19. trilogy/ai/providers/anthropic.py +106 -0
  20. trilogy/ai/providers/base.py +24 -0
  21. trilogy/ai/providers/google.py +146 -0
  22. trilogy/ai/providers/openai.py +89 -0
  23. trilogy/ai/providers/utils.py +68 -0
  24. trilogy/authoring/README.md +3 -0
  25. trilogy/authoring/__init__.py +148 -0
  26. trilogy/constants.py +113 -0
  27. trilogy/core/README.md +52 -0
  28. trilogy/core/__init__.py +0 -0
  29. trilogy/core/constants.py +6 -0
  30. trilogy/core/enums.py +443 -0
  31. trilogy/core/env_processor.py +120 -0
  32. trilogy/core/environment_helpers.py +320 -0
  33. trilogy/core/ergonomics.py +193 -0
  34. trilogy/core/exceptions.py +123 -0
  35. trilogy/core/functions.py +1227 -0
  36. trilogy/core/graph_models.py +139 -0
  37. trilogy/core/internal.py +85 -0
  38. trilogy/core/models/__init__.py +0 -0
  39. trilogy/core/models/author.py +2669 -0
  40. trilogy/core/models/build.py +2521 -0
  41. trilogy/core/models/build_environment.py +180 -0
  42. trilogy/core/models/core.py +501 -0
  43. trilogy/core/models/datasource.py +322 -0
  44. trilogy/core/models/environment.py +751 -0
  45. trilogy/core/models/execute.py +1177 -0
  46. trilogy/core/optimization.py +251 -0
  47. trilogy/core/optimizations/__init__.py +12 -0
  48. trilogy/core/optimizations/base_optimization.py +17 -0
  49. trilogy/core/optimizations/hide_unused_concept.py +47 -0
  50. trilogy/core/optimizations/inline_datasource.py +102 -0
  51. trilogy/core/optimizations/predicate_pushdown.py +245 -0
  52. trilogy/core/processing/README.md +94 -0
  53. trilogy/core/processing/READMEv2.md +121 -0
  54. trilogy/core/processing/VIRTUAL_UNNEST.md +30 -0
  55. trilogy/core/processing/__init__.py +0 -0
  56. trilogy/core/processing/concept_strategies_v3.py +508 -0
  57. trilogy/core/processing/constants.py +15 -0
  58. trilogy/core/processing/discovery_node_factory.py +451 -0
  59. trilogy/core/processing/discovery_utility.py +548 -0
  60. trilogy/core/processing/discovery_validation.py +167 -0
  61. trilogy/core/processing/graph_utils.py +43 -0
  62. trilogy/core/processing/node_generators/README.md +9 -0
  63. trilogy/core/processing/node_generators/__init__.py +31 -0
  64. trilogy/core/processing/node_generators/basic_node.py +160 -0
  65. trilogy/core/processing/node_generators/common.py +268 -0
  66. trilogy/core/processing/node_generators/constant_node.py +38 -0
  67. trilogy/core/processing/node_generators/filter_node.py +315 -0
  68. trilogy/core/processing/node_generators/group_node.py +213 -0
  69. trilogy/core/processing/node_generators/group_to_node.py +117 -0
  70. trilogy/core/processing/node_generators/multiselect_node.py +205 -0
  71. trilogy/core/processing/node_generators/node_merge_node.py +653 -0
  72. trilogy/core/processing/node_generators/recursive_node.py +88 -0
  73. trilogy/core/processing/node_generators/rowset_node.py +165 -0
  74. trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  75. trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +261 -0
  76. trilogy/core/processing/node_generators/select_merge_node.py +748 -0
  77. trilogy/core/processing/node_generators/select_node.py +95 -0
  78. trilogy/core/processing/node_generators/synonym_node.py +98 -0
  79. trilogy/core/processing/node_generators/union_node.py +91 -0
  80. trilogy/core/processing/node_generators/unnest_node.py +182 -0
  81. trilogy/core/processing/node_generators/window_node.py +201 -0
  82. trilogy/core/processing/nodes/README.md +28 -0
  83. trilogy/core/processing/nodes/__init__.py +179 -0
  84. trilogy/core/processing/nodes/base_node.py +519 -0
  85. trilogy/core/processing/nodes/filter_node.py +75 -0
  86. trilogy/core/processing/nodes/group_node.py +194 -0
  87. trilogy/core/processing/nodes/merge_node.py +420 -0
  88. trilogy/core/processing/nodes/recursive_node.py +46 -0
  89. trilogy/core/processing/nodes/select_node_v2.py +242 -0
  90. trilogy/core/processing/nodes/union_node.py +53 -0
  91. trilogy/core/processing/nodes/unnest_node.py +62 -0
  92. trilogy/core/processing/nodes/window_node.py +56 -0
  93. trilogy/core/processing/utility.py +823 -0
  94. trilogy/core/query_processor.py +596 -0
  95. trilogy/core/statements/README.md +35 -0
  96. trilogy/core/statements/__init__.py +0 -0
  97. trilogy/core/statements/author.py +536 -0
  98. trilogy/core/statements/build.py +0 -0
  99. trilogy/core/statements/common.py +20 -0
  100. trilogy/core/statements/execute.py +155 -0
  101. trilogy/core/table_processor.py +66 -0
  102. trilogy/core/utility.py +8 -0
  103. trilogy/core/validation/README.md +46 -0
  104. trilogy/core/validation/__init__.py +0 -0
  105. trilogy/core/validation/common.py +161 -0
  106. trilogy/core/validation/concept.py +146 -0
  107. trilogy/core/validation/datasource.py +227 -0
  108. trilogy/core/validation/environment.py +73 -0
  109. trilogy/core/validation/fix.py +256 -0
  110. trilogy/dialect/__init__.py +32 -0
  111. trilogy/dialect/base.py +1392 -0
  112. trilogy/dialect/bigquery.py +308 -0
  113. trilogy/dialect/common.py +147 -0
  114. trilogy/dialect/config.py +144 -0
  115. trilogy/dialect/dataframe.py +50 -0
  116. trilogy/dialect/duckdb.py +231 -0
  117. trilogy/dialect/enums.py +147 -0
  118. trilogy/dialect/metadata.py +173 -0
  119. trilogy/dialect/mock.py +190 -0
  120. trilogy/dialect/postgres.py +117 -0
  121. trilogy/dialect/presto.py +110 -0
  122. trilogy/dialect/results.py +89 -0
  123. trilogy/dialect/snowflake.py +129 -0
  124. trilogy/dialect/sql_server.py +137 -0
  125. trilogy/engine.py +48 -0
  126. trilogy/execution/config.py +75 -0
  127. trilogy/executor.py +568 -0
  128. trilogy/hooks/__init__.py +4 -0
  129. trilogy/hooks/base_hook.py +40 -0
  130. trilogy/hooks/graph_hook.py +139 -0
  131. trilogy/hooks/query_debugger.py +166 -0
  132. trilogy/metadata/__init__.py +0 -0
  133. trilogy/parser.py +10 -0
  134. trilogy/parsing/README.md +21 -0
  135. trilogy/parsing/__init__.py +0 -0
  136. trilogy/parsing/common.py +1069 -0
  137. trilogy/parsing/config.py +5 -0
  138. trilogy/parsing/exceptions.py +8 -0
  139. trilogy/parsing/helpers.py +1 -0
  140. trilogy/parsing/parse_engine.py +2813 -0
  141. trilogy/parsing/render.py +769 -0
  142. trilogy/parsing/trilogy.lark +540 -0
  143. trilogy/py.typed +0 -0
  144. trilogy/render.py +42 -0
  145. trilogy/scripts/README.md +9 -0
  146. trilogy/scripts/__init__.py +0 -0
  147. trilogy/scripts/agent.py +41 -0
  148. trilogy/scripts/agent_info.py +303 -0
  149. trilogy/scripts/common.py +355 -0
  150. trilogy/scripts/dependency/Cargo.lock +617 -0
  151. trilogy/scripts/dependency/Cargo.toml +39 -0
  152. trilogy/scripts/dependency/README.md +131 -0
  153. trilogy/scripts/dependency/build.sh +25 -0
  154. trilogy/scripts/dependency/src/directory_resolver.rs +177 -0
  155. trilogy/scripts/dependency/src/lib.rs +16 -0
  156. trilogy/scripts/dependency/src/main.rs +770 -0
  157. trilogy/scripts/dependency/src/parser.rs +435 -0
  158. trilogy/scripts/dependency/src/preql.pest +208 -0
  159. trilogy/scripts/dependency/src/python_bindings.rs +303 -0
  160. trilogy/scripts/dependency/src/resolver.rs +716 -0
  161. trilogy/scripts/dependency/tests/base.preql +3 -0
  162. trilogy/scripts/dependency/tests/cli_integration.rs +377 -0
  163. trilogy/scripts/dependency/tests/customer.preql +6 -0
  164. trilogy/scripts/dependency/tests/main.preql +9 -0
  165. trilogy/scripts/dependency/tests/orders.preql +7 -0
  166. trilogy/scripts/dependency/tests/test_data/base.preql +9 -0
  167. trilogy/scripts/dependency/tests/test_data/consumer.preql +1 -0
  168. trilogy/scripts/dependency.py +323 -0
  169. trilogy/scripts/display.py +512 -0
  170. trilogy/scripts/environment.py +46 -0
  171. trilogy/scripts/fmt.py +32 -0
  172. trilogy/scripts/ingest.py +471 -0
  173. trilogy/scripts/ingest_helpers/__init__.py +1 -0
  174. trilogy/scripts/ingest_helpers/foreign_keys.py +123 -0
  175. trilogy/scripts/ingest_helpers/formatting.py +93 -0
  176. trilogy/scripts/ingest_helpers/typing.py +161 -0
  177. trilogy/scripts/init.py +105 -0
  178. trilogy/scripts/parallel_execution.py +713 -0
  179. trilogy/scripts/plan.py +189 -0
  180. trilogy/scripts/run.py +63 -0
  181. trilogy/scripts/serve.py +140 -0
  182. trilogy/scripts/serve_helpers/__init__.py +41 -0
  183. trilogy/scripts/serve_helpers/file_discovery.py +142 -0
  184. trilogy/scripts/serve_helpers/index_generation.py +206 -0
  185. trilogy/scripts/serve_helpers/models.py +38 -0
  186. trilogy/scripts/single_execution.py +131 -0
  187. trilogy/scripts/testing.py +119 -0
  188. trilogy/scripts/trilogy.py +68 -0
  189. trilogy/std/__init__.py +0 -0
  190. trilogy/std/color.preql +3 -0
  191. trilogy/std/date.preql +13 -0
  192. trilogy/std/display.preql +18 -0
  193. trilogy/std/geography.preql +22 -0
  194. trilogy/std/metric.preql +15 -0
  195. trilogy/std/money.preql +67 -0
  196. trilogy/std/net.preql +14 -0
  197. trilogy/std/ranking.preql +7 -0
  198. trilogy/std/report.preql +5 -0
  199. trilogy/std/semantic.preql +6 -0
  200. 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()