pytrilogy 0.3.149__cp313-cp313-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.
- LICENSE.md +19 -0
- _preql_import_resolver/__init__.py +5 -0
- _preql_import_resolver/_preql_import_resolver.cp313-win_amd64.pyd +0 -0
- pytrilogy-0.3.149.dist-info/METADATA +555 -0
- pytrilogy-0.3.149.dist-info/RECORD +207 -0
- pytrilogy-0.3.149.dist-info/WHEEL +4 -0
- pytrilogy-0.3.149.dist-info/entry_points.txt +2 -0
- pytrilogy-0.3.149.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 +2670 -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 +436 -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 +846 -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 +1432 -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 +397 -0
- trilogy/dialect/enums.py +151 -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/exceptions.py +26 -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 +406 -0
- trilogy/executor.py +692 -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 +2876 -0
- trilogy/parsing/render.py +775 -0
- trilogy/parsing/trilogy.lark +546 -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 +432 -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 +487 -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 +762 -0
- trilogy/scripts/plan.py +189 -0
- trilogy/scripts/refresh.py +161 -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 +143 -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,536 @@
|
|
|
1
|
+
from functools import cached_property
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Annotated, List, Optional, Union
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field, computed_field, field_validator
|
|
6
|
+
from pydantic.functional_validators import PlainValidator
|
|
7
|
+
|
|
8
|
+
from trilogy.constants import CONFIG, DEFAULT_NAMESPACE
|
|
9
|
+
from trilogy.core.enums import (
|
|
10
|
+
ConceptSource,
|
|
11
|
+
CreateMode,
|
|
12
|
+
FunctionClass,
|
|
13
|
+
IOType,
|
|
14
|
+
Modifier,
|
|
15
|
+
PersistMode,
|
|
16
|
+
PublishAction,
|
|
17
|
+
ShowCategory,
|
|
18
|
+
ValidationScope,
|
|
19
|
+
)
|
|
20
|
+
from trilogy.core.models.author import (
|
|
21
|
+
AggregateWrapper,
|
|
22
|
+
AlignClause,
|
|
23
|
+
ArgBinding,
|
|
24
|
+
Concept,
|
|
25
|
+
ConceptRef,
|
|
26
|
+
CustomType,
|
|
27
|
+
DeriveClause,
|
|
28
|
+
Expr,
|
|
29
|
+
FilterItem,
|
|
30
|
+
Function,
|
|
31
|
+
FunctionCallWrapper,
|
|
32
|
+
Grain,
|
|
33
|
+
HasUUID,
|
|
34
|
+
HavingClause,
|
|
35
|
+
Metadata,
|
|
36
|
+
MultiSelectLineage,
|
|
37
|
+
OrderBy,
|
|
38
|
+
Parenthetical,
|
|
39
|
+
SelectLineage,
|
|
40
|
+
UndefinedConcept,
|
|
41
|
+
WhereClause,
|
|
42
|
+
WindowItem,
|
|
43
|
+
)
|
|
44
|
+
from trilogy.core.models.datasource import Address, ColumnAssignment, Datasource
|
|
45
|
+
from trilogy.core.models.environment import (
|
|
46
|
+
Environment,
|
|
47
|
+
EnvironmentConceptDict,
|
|
48
|
+
validate_concepts,
|
|
49
|
+
)
|
|
50
|
+
from trilogy.core.statements.common import SelectTypeMixin
|
|
51
|
+
from trilogy.utility import unique
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ConceptTransform(BaseModel):
|
|
55
|
+
function: (
|
|
56
|
+
Function
|
|
57
|
+
| FilterItem
|
|
58
|
+
| WindowItem
|
|
59
|
+
| AggregateWrapper
|
|
60
|
+
| FunctionCallWrapper
|
|
61
|
+
| Parenthetical
|
|
62
|
+
)
|
|
63
|
+
output: Concept # this has to be a full concept, as it may not exist in environment
|
|
64
|
+
modifiers: List[Modifier] = Field(default_factory=list)
|
|
65
|
+
|
|
66
|
+
def with_merge(self, source: Concept, target: Concept, modifiers: List[Modifier]):
|
|
67
|
+
return ConceptTransform(
|
|
68
|
+
function=self.function.with_merge(source, target, modifiers),
|
|
69
|
+
output=self.output.with_merge(source, target, modifiers),
|
|
70
|
+
modifiers=self.modifiers + modifiers,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def with_namespace(self, namespace: str) -> "ConceptTransform":
|
|
74
|
+
return ConceptTransform(
|
|
75
|
+
function=self.function.with_namespace(namespace),
|
|
76
|
+
output=self.output.with_namespace(namespace),
|
|
77
|
+
modifiers=self.modifiers,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class SelectItem(BaseModel):
|
|
82
|
+
content: Union[ConceptTransform, ConceptRef]
|
|
83
|
+
modifiers: List[Modifier] = Field(default_factory=list)
|
|
84
|
+
|
|
85
|
+
@field_validator("content", mode="before")
|
|
86
|
+
def parse_content(cls, v):
|
|
87
|
+
if isinstance(v, Concept):
|
|
88
|
+
return v.reference
|
|
89
|
+
return v
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def concept(self) -> ConceptRef:
|
|
93
|
+
if isinstance(self.content, (ConceptRef)):
|
|
94
|
+
return self.content
|
|
95
|
+
elif isinstance(self.content, Concept):
|
|
96
|
+
return self.content.reference
|
|
97
|
+
return self.content.output.reference
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def is_undefined(self) -> bool:
|
|
101
|
+
return True if isinstance(self.content, UndefinedConcept) else False
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
|
|
105
|
+
selection: List[SelectItem]
|
|
106
|
+
order_by: Optional[OrderBy] = None
|
|
107
|
+
limit: Optional[int] = None
|
|
108
|
+
meta: Metadata = Field(default_factory=lambda: Metadata())
|
|
109
|
+
local_concepts: Annotated[
|
|
110
|
+
EnvironmentConceptDict, PlainValidator(validate_concepts)
|
|
111
|
+
] = Field(default_factory=EnvironmentConceptDict)
|
|
112
|
+
grain: Grain = Field(default_factory=Grain)
|
|
113
|
+
|
|
114
|
+
def as_lineage(self, environment: Environment) -> SelectLineage:
|
|
115
|
+
derived = [
|
|
116
|
+
x.concept.address
|
|
117
|
+
for x in self.selection
|
|
118
|
+
if isinstance(x.content, ConceptTransform)
|
|
119
|
+
]
|
|
120
|
+
return SelectLineage(
|
|
121
|
+
selection=[
|
|
122
|
+
environment.concepts[x.concept.address].reference
|
|
123
|
+
for x in self.selection
|
|
124
|
+
],
|
|
125
|
+
order_by=self.order_by,
|
|
126
|
+
limit=self.limit,
|
|
127
|
+
where_clause=self.where_clause,
|
|
128
|
+
having_clause=self.having_clause,
|
|
129
|
+
local_concepts={
|
|
130
|
+
k: v for k, v in self.local_concepts.items() if k in derived
|
|
131
|
+
},
|
|
132
|
+
hidden_components=self.hidden_components,
|
|
133
|
+
grain=self.grain,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def from_inputs(
|
|
138
|
+
cls,
|
|
139
|
+
environment: Environment,
|
|
140
|
+
selection: List[SelectItem],
|
|
141
|
+
order_by: OrderBy | None = None,
|
|
142
|
+
limit: int | None = None,
|
|
143
|
+
meta: Metadata | None = None,
|
|
144
|
+
where_clause: WhereClause | None = None,
|
|
145
|
+
having_clause: HavingClause | None = None,
|
|
146
|
+
) -> "SelectStatement":
|
|
147
|
+
output = SelectStatement(
|
|
148
|
+
selection=selection,
|
|
149
|
+
where_clause=where_clause,
|
|
150
|
+
having_clause=having_clause,
|
|
151
|
+
limit=limit,
|
|
152
|
+
order_by=order_by,
|
|
153
|
+
meta=meta or Metadata(),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
output.grain = output.calculate_grain(environment, output.local_concepts)
|
|
157
|
+
|
|
158
|
+
for x in selection:
|
|
159
|
+
if x.is_undefined and environment.concepts.fail_on_missing:
|
|
160
|
+
environment.concepts.raise_undefined(
|
|
161
|
+
x.concept.address, meta.line_number if meta else None
|
|
162
|
+
)
|
|
163
|
+
elif isinstance(x.content, ConceptTransform):
|
|
164
|
+
if isinstance(x.content.output, UndefinedConcept):
|
|
165
|
+
continue
|
|
166
|
+
if CONFIG.parsing.select_as_definition and not environment.frozen:
|
|
167
|
+
if x.concept.address not in environment.concepts:
|
|
168
|
+
environment.add_concept(x.content.output, add_derived=False)
|
|
169
|
+
elif x.concept.address in environment.concepts:
|
|
170
|
+
version = environment.concepts[x.concept.address]
|
|
171
|
+
if version.metadata.concept_source == ConceptSource.SELECT:
|
|
172
|
+
environment.add_concept(
|
|
173
|
+
x.content.output, force=True, add_derived=False
|
|
174
|
+
)
|
|
175
|
+
x.content.output = x.content.output.set_select_grain(
|
|
176
|
+
output.grain, environment
|
|
177
|
+
)
|
|
178
|
+
# we might not need this
|
|
179
|
+
output.local_concepts[x.content.output.address] = x.content.output
|
|
180
|
+
|
|
181
|
+
elif isinstance(x.content, ConceptRef):
|
|
182
|
+
output.local_concepts[x.content.address] = environment.concepts[
|
|
183
|
+
x.content.address
|
|
184
|
+
]
|
|
185
|
+
output.grain = output.calculate_grain(environment, output.local_concepts)
|
|
186
|
+
output.validate_syntax(environment)
|
|
187
|
+
return output
|
|
188
|
+
|
|
189
|
+
def calculate_grain(
|
|
190
|
+
self,
|
|
191
|
+
environment: Environment | None = None,
|
|
192
|
+
local_concepts: dict[str, Concept] | None = None,
|
|
193
|
+
) -> Grain:
|
|
194
|
+
targets = []
|
|
195
|
+
for x in self.selection:
|
|
196
|
+
targets.append(x.concept)
|
|
197
|
+
|
|
198
|
+
result = Grain.from_concepts(
|
|
199
|
+
targets,
|
|
200
|
+
where_clause=self.where_clause,
|
|
201
|
+
environment=environment,
|
|
202
|
+
local_concepts=local_concepts,
|
|
203
|
+
)
|
|
204
|
+
return result
|
|
205
|
+
|
|
206
|
+
def validate_syntax(self, environment: Environment):
|
|
207
|
+
if self.where_clause:
|
|
208
|
+
for x in self.where_clause.concept_arguments:
|
|
209
|
+
if isinstance(x, UndefinedConcept):
|
|
210
|
+
validate = environment.concepts.get(x.address)
|
|
211
|
+
if validate and self.where_clause:
|
|
212
|
+
self.where_clause = (
|
|
213
|
+
self.where_clause.with_reference_replacement(
|
|
214
|
+
x.address, validate.reference
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
else:
|
|
218
|
+
environment.concepts.raise_undefined(
|
|
219
|
+
x.address, x.metadata.line_number if x.metadata else None
|
|
220
|
+
)
|
|
221
|
+
all_in_output = [x for x in self.output_components]
|
|
222
|
+
if self.where_clause:
|
|
223
|
+
for cref in self.where_clause.concept_arguments:
|
|
224
|
+
concept = environment.concepts[cref.address]
|
|
225
|
+
if isinstance(concept, UndefinedConcept):
|
|
226
|
+
continue
|
|
227
|
+
if (
|
|
228
|
+
concept.lineage
|
|
229
|
+
and isinstance(concept.lineage, Function)
|
|
230
|
+
and concept.lineage.operator
|
|
231
|
+
in FunctionClass.AGGREGATE_FUNCTIONS.value
|
|
232
|
+
):
|
|
233
|
+
if concept.address in self.locally_derived:
|
|
234
|
+
raise SyntaxError(
|
|
235
|
+
f"Cannot reference an aggregate derived in the select ({concept.address}) in the same statement where clause; move to the HAVING clause instead; Line: {self.meta.line_number}"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
if (
|
|
239
|
+
concept.lineage
|
|
240
|
+
and isinstance(concept.lineage, AggregateWrapper)
|
|
241
|
+
and concept.lineage.function.operator
|
|
242
|
+
in FunctionClass.AGGREGATE_FUNCTIONS.value
|
|
243
|
+
):
|
|
244
|
+
if concept.address in self.locally_derived:
|
|
245
|
+
raise SyntaxError(
|
|
246
|
+
f"Cannot reference an aggregate derived in the select ({concept.address}) in the same statement where clause; move to the HAVING clause instead; Line: {self.meta.line_number}"
|
|
247
|
+
)
|
|
248
|
+
if self.having_clause:
|
|
249
|
+
for cref in self.having_clause.concept_arguments:
|
|
250
|
+
if cref.address not in [x for x in self.output_components]:
|
|
251
|
+
raise SyntaxError(
|
|
252
|
+
f"Cannot reference a column ({cref.address}) that is not in the select projection in the HAVING clause, move to WHERE; Line: {self.meta.line_number}"
|
|
253
|
+
)
|
|
254
|
+
if self.order_by:
|
|
255
|
+
for cref in self.order_by.concept_arguments:
|
|
256
|
+
if cref.address not in all_in_output:
|
|
257
|
+
raise SyntaxError(
|
|
258
|
+
f"Cannot order by column {cref.address} that is not in the output projection; line: {self.meta.line_number}"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
def __str__(self):
|
|
262
|
+
from trilogy.parsing.render import render_query
|
|
263
|
+
|
|
264
|
+
return render_query(self)
|
|
265
|
+
|
|
266
|
+
@field_validator("selection", mode="before")
|
|
267
|
+
@classmethod
|
|
268
|
+
def selection_validation(cls, v):
|
|
269
|
+
new = []
|
|
270
|
+
for item in v:
|
|
271
|
+
if isinstance(item, (Concept, ConceptTransform)):
|
|
272
|
+
new.append(SelectItem(content=item))
|
|
273
|
+
else:
|
|
274
|
+
new.append(item)
|
|
275
|
+
return new
|
|
276
|
+
|
|
277
|
+
@property
|
|
278
|
+
def locally_derived(self) -> set[str]:
|
|
279
|
+
locally_derived: set[str] = set()
|
|
280
|
+
for item in self.selection:
|
|
281
|
+
if isinstance(item.content, ConceptTransform):
|
|
282
|
+
locally_derived.add(item.concept.address)
|
|
283
|
+
return locally_derived
|
|
284
|
+
|
|
285
|
+
@property
|
|
286
|
+
def output_components(self) -> List[ConceptRef]:
|
|
287
|
+
return [x.concept for x in self.selection]
|
|
288
|
+
|
|
289
|
+
@property
|
|
290
|
+
def hidden_components(self) -> set[str]:
|
|
291
|
+
return set(
|
|
292
|
+
x.concept.address for x in self.selection if Modifier.HIDDEN in x.modifiers
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
def to_datasource(
|
|
296
|
+
self,
|
|
297
|
+
namespace: str,
|
|
298
|
+
name: str,
|
|
299
|
+
address: Address,
|
|
300
|
+
environment: Environment,
|
|
301
|
+
grain: Grain | None = None,
|
|
302
|
+
) -> Datasource:
|
|
303
|
+
if self.where_clause or self.having_clause:
|
|
304
|
+
modifiers = [Modifier.PARTIAL]
|
|
305
|
+
else:
|
|
306
|
+
modifiers = []
|
|
307
|
+
columns = [
|
|
308
|
+
# TODO: replace hardcoded replacement here
|
|
309
|
+
# if the concept is a locally derived concept, it cannot ever be partial
|
|
310
|
+
# but if it's a concept pulled in from upstream and we have a where clause, it should be partial
|
|
311
|
+
ColumnAssignment(
|
|
312
|
+
alias=(
|
|
313
|
+
c.address.replace(".", "_")
|
|
314
|
+
if c.namespace != DEFAULT_NAMESPACE
|
|
315
|
+
else c.name
|
|
316
|
+
),
|
|
317
|
+
concept=environment.concepts[c.address].reference,
|
|
318
|
+
modifiers=modifiers if c.address not in self.locally_derived else [],
|
|
319
|
+
)
|
|
320
|
+
for c in self.output_components
|
|
321
|
+
]
|
|
322
|
+
|
|
323
|
+
condition = None
|
|
324
|
+
if self.where_clause:
|
|
325
|
+
condition = self.where_clause.conditional
|
|
326
|
+
if self.having_clause:
|
|
327
|
+
if condition:
|
|
328
|
+
condition = self.having_clause.conditional + condition
|
|
329
|
+
else:
|
|
330
|
+
condition = self.having_clause.conditional
|
|
331
|
+
|
|
332
|
+
new_datasource = Datasource(
|
|
333
|
+
name=name,
|
|
334
|
+
address=address,
|
|
335
|
+
grain=grain or self.grain,
|
|
336
|
+
columns=columns,
|
|
337
|
+
namespace=namespace,
|
|
338
|
+
non_partial_for=WhereClause(conditional=condition) if condition else None,
|
|
339
|
+
)
|
|
340
|
+
return new_datasource
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class RawSQLStatement(BaseModel):
|
|
344
|
+
text: str
|
|
345
|
+
meta: Optional[Metadata] = Field(default_factory=lambda: Metadata())
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
class CopyStatement(BaseModel):
|
|
349
|
+
target: str
|
|
350
|
+
target_type: IOType
|
|
351
|
+
meta: Optional[Metadata] = Field(default_factory=lambda: Metadata())
|
|
352
|
+
select: SelectStatement
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class MultiSelectStatement(HasUUID, SelectTypeMixin, BaseModel):
|
|
356
|
+
selects: List[SelectStatement]
|
|
357
|
+
align: AlignClause
|
|
358
|
+
namespace: str
|
|
359
|
+
derived_concepts: List[Concept]
|
|
360
|
+
order_by: Optional[OrderBy] = None
|
|
361
|
+
limit: Optional[int] = None
|
|
362
|
+
meta: Optional[Metadata] = Field(default_factory=lambda: Metadata())
|
|
363
|
+
local_concepts: Annotated[
|
|
364
|
+
EnvironmentConceptDict, PlainValidator(validate_concepts)
|
|
365
|
+
] = Field(default_factory=EnvironmentConceptDict)
|
|
366
|
+
derive: DeriveClause | None = None
|
|
367
|
+
|
|
368
|
+
def as_lineage(self, environment: Environment):
|
|
369
|
+
return MultiSelectLineage(
|
|
370
|
+
selects=[x.as_lineage(environment) for x in self.selects],
|
|
371
|
+
align=self.align,
|
|
372
|
+
derive=self.derive,
|
|
373
|
+
namespace=self.namespace,
|
|
374
|
+
# derived_concepts = self.derived_concepts,
|
|
375
|
+
limit=self.limit,
|
|
376
|
+
order_by=self.order_by,
|
|
377
|
+
where_clause=self.where_clause,
|
|
378
|
+
having_clause=self.having_clause,
|
|
379
|
+
hidden_components=self.hidden_components,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
def __repr__(self):
|
|
383
|
+
return "MultiSelect<" + " MERGE ".join([str(s) for s in self.selects]) + ">"
|
|
384
|
+
|
|
385
|
+
@property
|
|
386
|
+
def grain(self):
|
|
387
|
+
base = Grain()
|
|
388
|
+
for select in self.selects:
|
|
389
|
+
base += select.grain
|
|
390
|
+
return base
|
|
391
|
+
|
|
392
|
+
@property
|
|
393
|
+
def output_components(self) -> List[ConceptRef]:
|
|
394
|
+
output = [x.reference for x in self.derived_concepts]
|
|
395
|
+
for select in self.selects:
|
|
396
|
+
output += [
|
|
397
|
+
x
|
|
398
|
+
for x in select.output_components
|
|
399
|
+
if x.address not in select.hidden_components
|
|
400
|
+
]
|
|
401
|
+
return unique(output, "address")
|
|
402
|
+
|
|
403
|
+
@computed_field # type: ignore
|
|
404
|
+
@cached_property
|
|
405
|
+
def hidden_components(self) -> set[str]:
|
|
406
|
+
output: set[str] = set()
|
|
407
|
+
for select in self.selects:
|
|
408
|
+
output = output.union(select.hidden_components)
|
|
409
|
+
return output
|
|
410
|
+
|
|
411
|
+
@property
|
|
412
|
+
def locally_derived(self) -> set[str]:
|
|
413
|
+
locally_derived: set[str] = set([x.address for x in self.derived_concepts])
|
|
414
|
+
for select in self.selects:
|
|
415
|
+
locally_derived = locally_derived.union(select.locally_derived)
|
|
416
|
+
return locally_derived
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
class RowsetDerivationStatement(HasUUID, BaseModel):
|
|
420
|
+
name: str
|
|
421
|
+
select: SelectStatement | MultiSelectStatement
|
|
422
|
+
namespace: str
|
|
423
|
+
|
|
424
|
+
def __repr__(self):
|
|
425
|
+
return f"RowsetDerivation<{str(self.select)}>"
|
|
426
|
+
|
|
427
|
+
def __str__(self):
|
|
428
|
+
return self.__repr__()
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
class MergeStatementV2(HasUUID, BaseModel):
|
|
432
|
+
sources: list[Concept]
|
|
433
|
+
targets: dict[str, Concept]
|
|
434
|
+
source_wildcard: str | None = None
|
|
435
|
+
target_wildcard: str | None = None
|
|
436
|
+
modifiers: List[Modifier] = Field(default_factory=list)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
class KeyMergeStatement(HasUUID, BaseModel):
|
|
440
|
+
keys: set[str]
|
|
441
|
+
target: ConceptRef
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
class ImportStatement(HasUUID, BaseModel):
|
|
445
|
+
# import abc.def as bar
|
|
446
|
+
# the bit after 'as', eg bar
|
|
447
|
+
alias: str
|
|
448
|
+
# the bit after import, abc.def
|
|
449
|
+
input_path: str
|
|
450
|
+
# what it actually resolves to, typically a filepath
|
|
451
|
+
path: Path
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
class PersistStatement(HasUUID, BaseModel):
|
|
455
|
+
datasource: Datasource
|
|
456
|
+
select: SelectStatement
|
|
457
|
+
persist_mode: PersistMode = PersistMode.OVERWRITE
|
|
458
|
+
partition_by: List[ConceptRef] = Field(default_factory=list)
|
|
459
|
+
meta: Optional[Metadata] = Field(default_factory=lambda: Metadata())
|
|
460
|
+
|
|
461
|
+
@property
|
|
462
|
+
def identifier(self):
|
|
463
|
+
return self.datasource.identifier
|
|
464
|
+
|
|
465
|
+
@property
|
|
466
|
+
def address(self):
|
|
467
|
+
return self.datasource.address
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
class ValidateStatement(BaseModel):
|
|
471
|
+
scope: ValidationScope
|
|
472
|
+
targets: list[str] | None = None
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
class MockStatement(BaseModel):
|
|
476
|
+
scope: ValidationScope
|
|
477
|
+
targets: list[str]
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
class PublishStatement(BaseModel):
|
|
481
|
+
scope: ValidationScope
|
|
482
|
+
targets: list[str]
|
|
483
|
+
action: PublishAction = PublishAction.PUBLISH
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
class CreateStatement(BaseModel):
|
|
487
|
+
scope: ValidationScope
|
|
488
|
+
create_mode: CreateMode = CreateMode.CREATE_OR_REPLACE
|
|
489
|
+
targets: list[str]
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
class ShowStatement(BaseModel):
|
|
493
|
+
content: SelectStatement | PersistStatement | ValidateStatement | ShowCategory
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
class Limit(BaseModel):
|
|
497
|
+
count: int
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
class ConceptDeclarationStatement(HasUUID, BaseModel):
|
|
501
|
+
concept: Concept
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
class ConceptDerivationStatement(BaseModel):
|
|
505
|
+
concept: Concept
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
class TypeDeclaration(BaseModel):
|
|
509
|
+
type: CustomType
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
class FunctionDeclaration(HasUUID, BaseModel):
|
|
513
|
+
name: str
|
|
514
|
+
args: list[ArgBinding]
|
|
515
|
+
expr: Expr
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
STATEMENT_TYPES = (
|
|
519
|
+
SelectStatement
|
|
520
|
+
| RawSQLStatement
|
|
521
|
+
| CopyStatement
|
|
522
|
+
| MultiSelectStatement
|
|
523
|
+
| RowsetDerivationStatement
|
|
524
|
+
| MergeStatementV2
|
|
525
|
+
| KeyMergeStatement
|
|
526
|
+
| ImportStatement
|
|
527
|
+
| PersistStatement
|
|
528
|
+
| ValidateStatement
|
|
529
|
+
| PublishStatement
|
|
530
|
+
| CreateStatement
|
|
531
|
+
| ShowStatement
|
|
532
|
+
| ConceptDeclarationStatement
|
|
533
|
+
| ConceptDerivationStatement
|
|
534
|
+
| TypeDeclaration
|
|
535
|
+
| FunctionDeclaration
|
|
536
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import List, Union
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
from trilogy.core.enums import IOType
|
|
6
|
+
from trilogy.core.models.author import ConceptRef, HavingClause, WhereClause
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CopyQueryMixin(BaseModel):
|
|
10
|
+
target: str
|
|
11
|
+
target_type: IOType
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SelectTypeMixin(BaseModel):
|
|
15
|
+
where_clause: Union["WhereClause", None] = Field(default=None)
|
|
16
|
+
having_clause: Union["HavingClause", None] = Field(default=None)
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def output_components(self) -> List[ConceptRef]:
|
|
20
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import List, Optional, Union
|
|
3
|
+
|
|
4
|
+
from trilogy.core.enums import (
|
|
5
|
+
CreateMode,
|
|
6
|
+
IOType,
|
|
7
|
+
PersistMode,
|
|
8
|
+
PublishAction,
|
|
9
|
+
ValidationScope,
|
|
10
|
+
)
|
|
11
|
+
from trilogy.core.models.author import ConceptRef, HavingClause, WhereClause
|
|
12
|
+
from trilogy.core.models.build import (
|
|
13
|
+
BuildConcept,
|
|
14
|
+
BuildDatasource,
|
|
15
|
+
BuildOrderBy,
|
|
16
|
+
)
|
|
17
|
+
from trilogy.core.models.core import DataType
|
|
18
|
+
from trilogy.core.models.datasource import Address, Datasource
|
|
19
|
+
from trilogy.core.models.environment import EnvironmentConceptDict
|
|
20
|
+
from trilogy.core.models.execute import CTE, UnionCTE
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class CopyQueryMixin:
|
|
25
|
+
target: str
|
|
26
|
+
target_type: IOType
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class MaterializedDataset:
|
|
31
|
+
address: Address
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class PersistQueryMixin:
|
|
36
|
+
output_to: MaterializedDataset
|
|
37
|
+
datasource: Datasource
|
|
38
|
+
persist_mode: PersistMode
|
|
39
|
+
partition_by: List[str]
|
|
40
|
+
partition_types: List[DataType]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class SelectTypeMixin:
|
|
45
|
+
where_clause: Union["WhereClause", None] = field(default=None)
|
|
46
|
+
having_clause: Union["HavingClause", None] = field(default=None)
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def output_components(self) -> List[ConceptRef]:
|
|
50
|
+
raise NotImplementedError
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class ProcessedQuery:
|
|
55
|
+
output_columns: List[ConceptRef]
|
|
56
|
+
ctes: List[CTE | UnionCTE]
|
|
57
|
+
base: CTE | UnionCTE
|
|
58
|
+
hidden_columns: set[str] = field(default_factory=set)
|
|
59
|
+
limit: Optional[int] = None
|
|
60
|
+
order_by: Optional[BuildOrderBy] = None
|
|
61
|
+
local_concepts: EnvironmentConceptDict = field(
|
|
62
|
+
default_factory=EnvironmentConceptDict
|
|
63
|
+
)
|
|
64
|
+
locally_derived: set[str] = field(default_factory=set)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class ProcessedQueryPersist(ProcessedQuery, PersistQueryMixin):
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class ProcessedCopyStatement(ProcessedQuery, CopyQueryMixin):
|
|
74
|
+
column_aliases: dict[str, str] = field(default_factory=dict)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass
|
|
78
|
+
class ProcessedRawSQLStatement:
|
|
79
|
+
text: str
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass
|
|
83
|
+
class ProcessedValidateStatement:
|
|
84
|
+
scope: ValidationScope
|
|
85
|
+
targets: Optional[List[str]]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass
|
|
89
|
+
class ProcessedMockStatement:
|
|
90
|
+
scope: ValidationScope
|
|
91
|
+
targets: list[str]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class ColumnInfo:
|
|
96
|
+
name: str
|
|
97
|
+
type: DataType
|
|
98
|
+
nullable: bool = True
|
|
99
|
+
primary_key: bool = False
|
|
100
|
+
description: Optional[str] = None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass
|
|
104
|
+
class CreateTableInfo:
|
|
105
|
+
name: str
|
|
106
|
+
columns: List[ColumnInfo]
|
|
107
|
+
partition_keys: list[str] = field(default_factory=list)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass
|
|
111
|
+
class ProcessedCreateStatement:
|
|
112
|
+
scope: ValidationScope
|
|
113
|
+
create_mode: CreateMode
|
|
114
|
+
targets: list[CreateTableInfo]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass
|
|
118
|
+
class ProcessedPublishStatement:
|
|
119
|
+
scope: ValidationScope
|
|
120
|
+
targets: list[str]
|
|
121
|
+
action: PublishAction
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass
|
|
125
|
+
class ProcessedStaticValueOutput:
|
|
126
|
+
values: List[dict]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@dataclass
|
|
130
|
+
class ProcessedShowStatement:
|
|
131
|
+
output_columns: List[ConceptRef]
|
|
132
|
+
output_values: List[
|
|
133
|
+
Union[
|
|
134
|
+
BuildConcept,
|
|
135
|
+
BuildDatasource,
|
|
136
|
+
ProcessedQuery,
|
|
137
|
+
ProcessedQueryPersist,
|
|
138
|
+
ProcessedCopyStatement,
|
|
139
|
+
ProcessedValidateStatement,
|
|
140
|
+
ProcessedStaticValueOutput,
|
|
141
|
+
]
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
PROCESSED_STATEMENT_TYPES = (
|
|
146
|
+
ProcessedCopyStatement
|
|
147
|
+
| ProcessedQuery
|
|
148
|
+
| ProcessedRawSQLStatement
|
|
149
|
+
| ProcessedQueryPersist
|
|
150
|
+
| ProcessedShowStatement
|
|
151
|
+
| ProcessedValidateStatement
|
|
152
|
+
| ProcessedCreateStatement
|
|
153
|
+
| ProcessedPublishStatement
|
|
154
|
+
| ProcessedMockStatement
|
|
155
|
+
)
|