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
trilogy/executor.py
ADDED
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
from functools import singledispatchmethod
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any, Generator, List, Optional
|
|
4
|
+
|
|
5
|
+
from sqlalchemy import text
|
|
6
|
+
|
|
7
|
+
from trilogy.constants import MagicConstants, Rendering, logger
|
|
8
|
+
from trilogy.core.enums import (
|
|
9
|
+
FunctionType,
|
|
10
|
+
Granularity,
|
|
11
|
+
IOType,
|
|
12
|
+
PersistMode,
|
|
13
|
+
ValidationScope,
|
|
14
|
+
)
|
|
15
|
+
from trilogy.core.models.author import Comment, Concept, Function
|
|
16
|
+
from trilogy.core.models.build import BuildFunction
|
|
17
|
+
from trilogy.core.models.core import ListWrapper, MapWrapper
|
|
18
|
+
from trilogy.core.models.datasource import Datasource
|
|
19
|
+
from trilogy.core.models.environment import Environment
|
|
20
|
+
from trilogy.core.statements.author import (
|
|
21
|
+
STATEMENT_TYPES,
|
|
22
|
+
ConceptDeclarationStatement,
|
|
23
|
+
CopyStatement,
|
|
24
|
+
CreateStatement,
|
|
25
|
+
ImportStatement,
|
|
26
|
+
MergeStatementV2,
|
|
27
|
+
MockStatement,
|
|
28
|
+
MultiSelectStatement,
|
|
29
|
+
PersistStatement,
|
|
30
|
+
PublishStatement,
|
|
31
|
+
RawSQLStatement,
|
|
32
|
+
SelectStatement,
|
|
33
|
+
ShowStatement,
|
|
34
|
+
ValidateStatement,
|
|
35
|
+
)
|
|
36
|
+
from trilogy.core.statements.execute import (
|
|
37
|
+
PROCESSED_STATEMENT_TYPES,
|
|
38
|
+
ProcessedCopyStatement,
|
|
39
|
+
ProcessedCreateStatement,
|
|
40
|
+
ProcessedMockStatement,
|
|
41
|
+
ProcessedPublishStatement,
|
|
42
|
+
ProcessedQuery,
|
|
43
|
+
ProcessedQueryPersist,
|
|
44
|
+
ProcessedRawSQLStatement,
|
|
45
|
+
ProcessedShowStatement,
|
|
46
|
+
ProcessedValidateStatement,
|
|
47
|
+
)
|
|
48
|
+
from trilogy.core.validation.common import (
|
|
49
|
+
ValidationTest,
|
|
50
|
+
)
|
|
51
|
+
from trilogy.dialect.base import BaseDialect
|
|
52
|
+
from trilogy.dialect.enums import Dialects
|
|
53
|
+
from trilogy.dialect.metadata import (
|
|
54
|
+
generate_result_set,
|
|
55
|
+
handle_concept_declaration,
|
|
56
|
+
handle_datasource,
|
|
57
|
+
handle_import_statement,
|
|
58
|
+
handle_merge_statement,
|
|
59
|
+
handle_processed_show_statement,
|
|
60
|
+
handle_processed_validate_statement,
|
|
61
|
+
handle_publish_statement,
|
|
62
|
+
handle_show_statement_outputs,
|
|
63
|
+
)
|
|
64
|
+
from trilogy.dialect.mock import handle_processed_mock_statement
|
|
65
|
+
from trilogy.engine import EngineConnection, ExecutionEngine, ResultProtocol
|
|
66
|
+
from trilogy.hooks.base_hook import BaseHook
|
|
67
|
+
from trilogy.parser import parse_text
|
|
68
|
+
from trilogy.render import get_dialect_generator
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Executor(object):
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
dialect: Dialects,
|
|
75
|
+
engine: ExecutionEngine,
|
|
76
|
+
environment: Optional[Environment] = None,
|
|
77
|
+
rendering: Rendering | None = None,
|
|
78
|
+
hooks: List[BaseHook] | None = None,
|
|
79
|
+
):
|
|
80
|
+
self.dialect: Dialects = dialect
|
|
81
|
+
self.engine = engine
|
|
82
|
+
self.environment = environment or Environment()
|
|
83
|
+
self.generator: BaseDialect
|
|
84
|
+
self.logger = logger
|
|
85
|
+
self.hooks = hooks
|
|
86
|
+
self.generator = get_dialect_generator(self.dialect, rendering)
|
|
87
|
+
self.connection = self.connect()
|
|
88
|
+
# TODO: make generic
|
|
89
|
+
if self.dialect == Dialects.DATAFRAME:
|
|
90
|
+
self.engine.setup(self.environment, self.connection)
|
|
91
|
+
|
|
92
|
+
def connect(self) -> EngineConnection:
|
|
93
|
+
self.connection = self.engine.connect()
|
|
94
|
+
self.connected = True
|
|
95
|
+
return self.connection
|
|
96
|
+
|
|
97
|
+
def close(self):
|
|
98
|
+
self.engine.dispose(close=True)
|
|
99
|
+
if self.dialect == Dialects.DUCK_DB:
|
|
100
|
+
import gc
|
|
101
|
+
|
|
102
|
+
gc.collect()
|
|
103
|
+
self.connected = False
|
|
104
|
+
|
|
105
|
+
def execute_statement(
|
|
106
|
+
self,
|
|
107
|
+
statement: PROCESSED_STATEMENT_TYPES,
|
|
108
|
+
) -> Optional[ResultProtocol]:
|
|
109
|
+
if isinstance(statement, STATEMENT_TYPES):
|
|
110
|
+
generate = self.generator.generate_queries(
|
|
111
|
+
self.environment, [statement], hooks=self.hooks
|
|
112
|
+
)
|
|
113
|
+
if not generate:
|
|
114
|
+
return None
|
|
115
|
+
statement = generate[0]
|
|
116
|
+
|
|
117
|
+
if not isinstance(statement, PROCESSED_STATEMENT_TYPES):
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
return self.execute_query(statement)
|
|
121
|
+
|
|
122
|
+
@singledispatchmethod
|
|
123
|
+
def execute_query(self, query) -> ResultProtocol | None:
|
|
124
|
+
raise NotImplementedError("Cannot execute type {}".format(type(query)))
|
|
125
|
+
|
|
126
|
+
@execute_query.register
|
|
127
|
+
def _(self, query: Comment) -> ResultProtocol | None:
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
@execute_query.register
|
|
131
|
+
def _(self, query: ConceptDeclarationStatement) -> ResultProtocol | None:
|
|
132
|
+
return handle_concept_declaration(query)
|
|
133
|
+
|
|
134
|
+
@execute_query.register
|
|
135
|
+
def _(self, query: Datasource) -> ResultProtocol | None:
|
|
136
|
+
return handle_datasource(query)
|
|
137
|
+
|
|
138
|
+
@execute_query.register
|
|
139
|
+
def _(self, query: str) -> ResultProtocol | None:
|
|
140
|
+
results = self.execute_text(query)
|
|
141
|
+
if results:
|
|
142
|
+
return results[-1]
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
@execute_query.register
|
|
146
|
+
def _(self, query: SelectStatement) -> ResultProtocol | None:
|
|
147
|
+
sql = self.generator.generate_queries(
|
|
148
|
+
self.environment, [query], hooks=self.hooks
|
|
149
|
+
)
|
|
150
|
+
return self.execute_query(sql[0])
|
|
151
|
+
|
|
152
|
+
@execute_query.register
|
|
153
|
+
def _(self, query: PersistStatement) -> ResultProtocol | None:
|
|
154
|
+
sql = self.generator.generate_queries(
|
|
155
|
+
self.environment, [query], hooks=self.hooks
|
|
156
|
+
)
|
|
157
|
+
return self.execute_query(sql[0])
|
|
158
|
+
|
|
159
|
+
@execute_query.register
|
|
160
|
+
def _(self, query: RawSQLStatement) -> ResultProtocol | None:
|
|
161
|
+
return self.execute_raw_sql(query.text)
|
|
162
|
+
|
|
163
|
+
@execute_query.register
|
|
164
|
+
def _(self, query: ShowStatement) -> ResultProtocol | None:
|
|
165
|
+
sql = self.generator.generate_queries(
|
|
166
|
+
self.environment, [query], hooks=self.hooks
|
|
167
|
+
)
|
|
168
|
+
return self.execute_query(sql[0])
|
|
169
|
+
|
|
170
|
+
@execute_query.register
|
|
171
|
+
def _(self, query: ProcessedShowStatement) -> ResultProtocol | None:
|
|
172
|
+
return handle_processed_show_statement(
|
|
173
|
+
query,
|
|
174
|
+
[
|
|
175
|
+
self.generator.compile_statement(x)
|
|
176
|
+
for x in query.output_values
|
|
177
|
+
if isinstance(x, (ProcessedQuery, ProcessedQueryPersist))
|
|
178
|
+
],
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
@execute_query.register
|
|
182
|
+
def _(self, query: ProcessedValidateStatement) -> ResultProtocol | None:
|
|
183
|
+
return handle_processed_validate_statement(
|
|
184
|
+
query, self.generator, self.validate_environment
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
@execute_query.register
|
|
188
|
+
def _(self, query: ProcessedMockStatement) -> ResultProtocol | None:
|
|
189
|
+
|
|
190
|
+
return handle_processed_mock_statement(query, self.environment, self)
|
|
191
|
+
|
|
192
|
+
@execute_query.register
|
|
193
|
+
def _(self, query: ProcessedCreateStatement) -> ResultProtocol | None:
|
|
194
|
+
sql = self.generator.compile_statement(query)
|
|
195
|
+
output = self.execute_raw_sql(sql)
|
|
196
|
+
return output
|
|
197
|
+
|
|
198
|
+
@execute_query.register
|
|
199
|
+
def _(self, query: ProcessedPublishStatement) -> ResultProtocol | None:
|
|
200
|
+
return handle_publish_statement(query, self.environment)
|
|
201
|
+
|
|
202
|
+
@execute_query.register
|
|
203
|
+
def _(self, query: ImportStatement) -> ResultProtocol | None:
|
|
204
|
+
return handle_import_statement(query)
|
|
205
|
+
|
|
206
|
+
@execute_query.register
|
|
207
|
+
def _(self, query: MergeStatementV2) -> ResultProtocol | None:
|
|
208
|
+
return handle_merge_statement(query, self.environment)
|
|
209
|
+
|
|
210
|
+
@execute_query.register
|
|
211
|
+
def _(self, query: ProcessedRawSQLStatement) -> ResultProtocol | None:
|
|
212
|
+
return self.execute_raw_sql(query.text)
|
|
213
|
+
|
|
214
|
+
@execute_query.register
|
|
215
|
+
def _(self, query: ProcessedQuery) -> ResultProtocol | None:
|
|
216
|
+
sql = self.generator.compile_statement(query)
|
|
217
|
+
output = self.execute_raw_sql(sql, local_concepts=query.local_concepts)
|
|
218
|
+
return output
|
|
219
|
+
|
|
220
|
+
@execute_query.register
|
|
221
|
+
def _(self, query: ProcessedQueryPersist) -> ResultProtocol | None:
|
|
222
|
+
sql = self.generator.compile_statement(query)
|
|
223
|
+
|
|
224
|
+
output = self.execute_raw_sql(sql, local_concepts=query.local_concepts)
|
|
225
|
+
if query.persist_mode == PersistMode.OVERWRITE:
|
|
226
|
+
self.environment.add_datasource(query.datasource)
|
|
227
|
+
return output
|
|
228
|
+
|
|
229
|
+
@execute_query.register
|
|
230
|
+
def _(self, query: ProcessedCopyStatement) -> ResultProtocol | None:
|
|
231
|
+
sql = self.generator.compile_statement(query)
|
|
232
|
+
output: ResultProtocol = self.execute_raw_sql(
|
|
233
|
+
sql, local_concepts=query.local_concepts
|
|
234
|
+
)
|
|
235
|
+
if query.target_type == IOType.CSV:
|
|
236
|
+
import csv
|
|
237
|
+
|
|
238
|
+
with open(query.target, "w", newline="", encoding="utf-8") as f:
|
|
239
|
+
outcsv = csv.writer(f)
|
|
240
|
+
outcsv.writerow(output.keys())
|
|
241
|
+
outcsv.writerows(output)
|
|
242
|
+
else:
|
|
243
|
+
raise NotImplementedError(f"Unsupported IO Type {query.target_type}")
|
|
244
|
+
# now return the query we ran through IO
|
|
245
|
+
# TODO: instead return how many rows were written?
|
|
246
|
+
return generate_result_set(
|
|
247
|
+
query.output_columns,
|
|
248
|
+
[self.generator.compile_statement(query)],
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
@singledispatchmethod
|
|
252
|
+
def generate_sql(self, command) -> list[str]:
|
|
253
|
+
raise NotImplementedError(
|
|
254
|
+
"Cannot generate sql for type {}".format(type(command))
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
@generate_sql.register # type: ignore
|
|
258
|
+
def _(self, command: ProcessedQuery) -> list[str]:
|
|
259
|
+
output = []
|
|
260
|
+
compiled_sql = self.generator.compile_statement(command)
|
|
261
|
+
output.append(compiled_sql)
|
|
262
|
+
return output
|
|
263
|
+
|
|
264
|
+
@generate_sql.register
|
|
265
|
+
def _(self, command: ProcessedShowStatement) -> list[str]:
|
|
266
|
+
output = []
|
|
267
|
+
for statement in command.output_values:
|
|
268
|
+
if isinstance(statement, (ProcessedQuery, ProcessedQueryPersist)):
|
|
269
|
+
compiled_sql = self.generator.compile_statement(statement)
|
|
270
|
+
output.append(compiled_sql)
|
|
271
|
+
return output
|
|
272
|
+
|
|
273
|
+
@generate_sql.register # type: ignore
|
|
274
|
+
def _(self, command: MultiSelectStatement) -> list[str]:
|
|
275
|
+
output = []
|
|
276
|
+
sql = self.generator.generate_queries(
|
|
277
|
+
self.environment, [command], hooks=self.hooks
|
|
278
|
+
)
|
|
279
|
+
for statement in sql:
|
|
280
|
+
compiled_sql = self.generator.compile_statement(statement)
|
|
281
|
+
output.append(compiled_sql)
|
|
282
|
+
return output
|
|
283
|
+
|
|
284
|
+
@generate_sql.register
|
|
285
|
+
def _(self, command: SelectStatement) -> list[str]:
|
|
286
|
+
output = []
|
|
287
|
+
sql = self.generator.generate_queries(
|
|
288
|
+
self.environment, [command], hooks=self.hooks
|
|
289
|
+
)
|
|
290
|
+
for statement in sql:
|
|
291
|
+
compiled_sql = self.generator.compile_statement(statement)
|
|
292
|
+
output.append(compiled_sql)
|
|
293
|
+
return output
|
|
294
|
+
|
|
295
|
+
@generate_sql.register
|
|
296
|
+
def _(self, command: ProcessedCreateStatement) -> list[str]:
|
|
297
|
+
output = []
|
|
298
|
+
compiled_sql = self.generator.compile_statement(command)
|
|
299
|
+
output.append(compiled_sql)
|
|
300
|
+
return output
|
|
301
|
+
|
|
302
|
+
@generate_sql.register
|
|
303
|
+
def _(self, command: ProcessedPublishStatement) -> list[str]:
|
|
304
|
+
output = []
|
|
305
|
+
compiled_sql = self.generator.compile_statement(command)
|
|
306
|
+
output.append(compiled_sql)
|
|
307
|
+
return output
|
|
308
|
+
|
|
309
|
+
@generate_sql.register
|
|
310
|
+
def _(self, command: str) -> list[str]:
|
|
311
|
+
_, parsed = parse_text(command, self.environment)
|
|
312
|
+
generatable = [
|
|
313
|
+
x
|
|
314
|
+
for x in parsed
|
|
315
|
+
if isinstance(x, (SelectStatement, PersistStatement, MultiSelectStatement))
|
|
316
|
+
]
|
|
317
|
+
sql = self.generator.generate_queries(
|
|
318
|
+
self.environment, generatable, hooks=self.hooks
|
|
319
|
+
)
|
|
320
|
+
output = []
|
|
321
|
+
for statement in sql:
|
|
322
|
+
if isinstance(statement, ProcessedShowStatement):
|
|
323
|
+
continue
|
|
324
|
+
compiled_sql = self.generator.compile_statement(statement)
|
|
325
|
+
output.append(compiled_sql)
|
|
326
|
+
return output
|
|
327
|
+
|
|
328
|
+
def parse_file(
|
|
329
|
+
self, file: str | Path, persist: bool = False
|
|
330
|
+
) -> list[PROCESSED_STATEMENT_TYPES]:
|
|
331
|
+
return list(self.parse_file_generator(file, persist=persist))
|
|
332
|
+
|
|
333
|
+
def parse_file_generator(
|
|
334
|
+
self, file: str | Path, persist: bool = False
|
|
335
|
+
) -> Generator[
|
|
336
|
+
PROCESSED_STATEMENT_TYPES,
|
|
337
|
+
None,
|
|
338
|
+
None,
|
|
339
|
+
]:
|
|
340
|
+
file = Path(file)
|
|
341
|
+
candidates = [file, self.environment.working_path / file]
|
|
342
|
+
err = None
|
|
343
|
+
for file in candidates:
|
|
344
|
+
try:
|
|
345
|
+
with open(file, "r") as f:
|
|
346
|
+
command = f.read()
|
|
347
|
+
return self.parse_text_generator(
|
|
348
|
+
command, persist=persist, root=file
|
|
349
|
+
)
|
|
350
|
+
except FileNotFoundError as e:
|
|
351
|
+
if not err:
|
|
352
|
+
err = e
|
|
353
|
+
continue
|
|
354
|
+
if err:
|
|
355
|
+
raise err
|
|
356
|
+
raise FileNotFoundError(f"File {file} not found")
|
|
357
|
+
|
|
358
|
+
def parse_text(
|
|
359
|
+
self, command: str, persist: bool = False, root: Path | None = None
|
|
360
|
+
) -> List[PROCESSED_STATEMENT_TYPES]:
|
|
361
|
+
return list(self.parse_text_generator(command, persist=persist, root=root))
|
|
362
|
+
|
|
363
|
+
def parse_text_generator(
|
|
364
|
+
self, command: str, persist: bool = False, root: Path | None = None
|
|
365
|
+
) -> Generator[
|
|
366
|
+
PROCESSED_STATEMENT_TYPES,
|
|
367
|
+
None,
|
|
368
|
+
None,
|
|
369
|
+
]:
|
|
370
|
+
"""Process a preql text command"""
|
|
371
|
+
_, parsed = parse_text(command, self.environment, root=root)
|
|
372
|
+
generatable = [
|
|
373
|
+
x
|
|
374
|
+
for x in parsed
|
|
375
|
+
if isinstance(
|
|
376
|
+
x,
|
|
377
|
+
(
|
|
378
|
+
SelectStatement,
|
|
379
|
+
PersistStatement,
|
|
380
|
+
MultiSelectStatement,
|
|
381
|
+
ShowStatement,
|
|
382
|
+
RawSQLStatement,
|
|
383
|
+
CopyStatement,
|
|
384
|
+
ValidateStatement,
|
|
385
|
+
CreateStatement,
|
|
386
|
+
PublishStatement,
|
|
387
|
+
MockStatement,
|
|
388
|
+
),
|
|
389
|
+
)
|
|
390
|
+
]
|
|
391
|
+
while generatable:
|
|
392
|
+
t = generatable.pop(0)
|
|
393
|
+
x = self.generator.generate_queries(
|
|
394
|
+
self.environment, [t], hooks=self.hooks
|
|
395
|
+
)[0]
|
|
396
|
+
|
|
397
|
+
yield x
|
|
398
|
+
|
|
399
|
+
if persist and isinstance(x, ProcessedQueryPersist):
|
|
400
|
+
self.environment.add_datasource(x.datasource)
|
|
401
|
+
|
|
402
|
+
def _atom_to_value(self, val: Any) -> Any:
|
|
403
|
+
if val == MagicConstants.NULL:
|
|
404
|
+
return None
|
|
405
|
+
return val
|
|
406
|
+
|
|
407
|
+
def _concept_to_value(
|
|
408
|
+
self,
|
|
409
|
+
concept: Concept,
|
|
410
|
+
local_concepts: dict[str, Concept] | None = None,
|
|
411
|
+
) -> Any:
|
|
412
|
+
if not concept.granularity == Granularity.SINGLE_ROW:
|
|
413
|
+
raise SyntaxError(
|
|
414
|
+
f"Cannot bind non-singleton concept {concept.address} ({concept.granularity}) to a parameter."
|
|
415
|
+
)
|
|
416
|
+
# TODO: to get rid of function here - need to figure out why it's getting passed in
|
|
417
|
+
if (
|
|
418
|
+
isinstance(concept.lineage, (BuildFunction, Function))
|
|
419
|
+
and concept.lineage.operator == FunctionType.CONSTANT
|
|
420
|
+
):
|
|
421
|
+
rval = concept.lineage.arguments[0]
|
|
422
|
+
if isinstance(rval, ListWrapper):
|
|
423
|
+
return [self._atom_to_value(x) for x in rval]
|
|
424
|
+
if isinstance(rval, MapWrapper):
|
|
425
|
+
# duckdb expects maps in this format as variables
|
|
426
|
+
if self.dialect == Dialects.DUCK_DB:
|
|
427
|
+
return {
|
|
428
|
+
"key": [self._atom_to_value(x) for x in rval],
|
|
429
|
+
"value": [self._atom_to_value(rval[x]) for x in rval],
|
|
430
|
+
}
|
|
431
|
+
return {k: self._atom_to_value(v) for k, v in rval.items()}
|
|
432
|
+
# if isinstance(rval, ConceptRef):
|
|
433
|
+
# return self._concept_to_value(self.environment.concepts[rval.address], local_concepts=local_concepts)
|
|
434
|
+
return rval
|
|
435
|
+
else:
|
|
436
|
+
results = self.execute_query(f"select {concept.name} limit 1;")
|
|
437
|
+
if results:
|
|
438
|
+
fetcher = results.fetchone()
|
|
439
|
+
if fetcher:
|
|
440
|
+
return fetcher[0]
|
|
441
|
+
return None
|
|
442
|
+
|
|
443
|
+
def _hydrate_param(
|
|
444
|
+
self, param: str, local_concepts: dict[str, Concept] | None = None
|
|
445
|
+
) -> Any:
|
|
446
|
+
matched = [
|
|
447
|
+
v
|
|
448
|
+
for v in self.environment.concepts.values()
|
|
449
|
+
if v.safe_address == param or v.address == param
|
|
450
|
+
]
|
|
451
|
+
if local_concepts and not matched:
|
|
452
|
+
matched = [
|
|
453
|
+
v
|
|
454
|
+
for v in local_concepts.values()
|
|
455
|
+
if v.safe_address == param or v.address == param
|
|
456
|
+
]
|
|
457
|
+
if not matched:
|
|
458
|
+
raise SyntaxError(f"No concept found for parameter {param};")
|
|
459
|
+
|
|
460
|
+
concept: Concept = matched.pop()
|
|
461
|
+
return self._concept_to_value(concept, local_concepts=local_concepts)
|
|
462
|
+
|
|
463
|
+
def execute_raw_sql(
|
|
464
|
+
self,
|
|
465
|
+
command: str | Path,
|
|
466
|
+
variables: dict | None = None,
|
|
467
|
+
local_concepts: dict[str, Concept] | None = None,
|
|
468
|
+
) -> ResultProtocol:
|
|
469
|
+
"""Run a command against the raw underlying
|
|
470
|
+
execution engine."""
|
|
471
|
+
final_params = None
|
|
472
|
+
if isinstance(command, Path):
|
|
473
|
+
with open(command, "r") as f:
|
|
474
|
+
command = f.read()
|
|
475
|
+
q = text(command)
|
|
476
|
+
if variables:
|
|
477
|
+
final_params = variables
|
|
478
|
+
else:
|
|
479
|
+
params = q.compile().params
|
|
480
|
+
if params:
|
|
481
|
+
final_params = {
|
|
482
|
+
x: self._hydrate_param(x, local_concepts=local_concepts)
|
|
483
|
+
for x in params
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if final_params:
|
|
487
|
+
return self.connection.execute(text(command), final_params)
|
|
488
|
+
return self.connection.execute(
|
|
489
|
+
text(command),
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
def execute_text(
|
|
493
|
+
self, command: str, non_interactive: bool = False
|
|
494
|
+
) -> List[ResultProtocol]:
|
|
495
|
+
if not self.connected:
|
|
496
|
+
self.connect()
|
|
497
|
+
|
|
498
|
+
"""Run a trilogy query expressed as text."""
|
|
499
|
+
output: list[ResultProtocol] = []
|
|
500
|
+
# connection = self.engine.connect()
|
|
501
|
+
for statement in self.parse_text_generator(command):
|
|
502
|
+
if isinstance(statement, ProcessedShowStatement):
|
|
503
|
+
results = handle_show_statement_outputs(
|
|
504
|
+
statement,
|
|
505
|
+
[
|
|
506
|
+
self.generator.compile_statement(x)
|
|
507
|
+
for x in statement.output_values
|
|
508
|
+
if isinstance(x, (ProcessedQuery, ProcessedQueryPersist))
|
|
509
|
+
],
|
|
510
|
+
self.environment,
|
|
511
|
+
self.generator,
|
|
512
|
+
)
|
|
513
|
+
output.extend(results)
|
|
514
|
+
continue
|
|
515
|
+
elif isinstance(statement, ProcessedValidateStatement):
|
|
516
|
+
validate_result = handle_processed_validate_statement(
|
|
517
|
+
statement, self.generator, self.validate_environment
|
|
518
|
+
)
|
|
519
|
+
if validate_result:
|
|
520
|
+
output.append(validate_result)
|
|
521
|
+
continue
|
|
522
|
+
if non_interactive:
|
|
523
|
+
if not isinstance(
|
|
524
|
+
statement,
|
|
525
|
+
(
|
|
526
|
+
ProcessedCopyStatement,
|
|
527
|
+
ProcessedQueryPersist,
|
|
528
|
+
ProcessedValidateStatement,
|
|
529
|
+
ProcessedRawSQLStatement,
|
|
530
|
+
ProcessedPublishStatement,
|
|
531
|
+
),
|
|
532
|
+
):
|
|
533
|
+
continue
|
|
534
|
+
result = self.execute_statement(statement)
|
|
535
|
+
if result:
|
|
536
|
+
output.append(result)
|
|
537
|
+
return output
|
|
538
|
+
|
|
539
|
+
def execute_file(
|
|
540
|
+
self, file: str | Path, non_interactive: bool = False
|
|
541
|
+
) -> List[ResultProtocol]:
|
|
542
|
+
file = Path(file)
|
|
543
|
+
candidates = [file, self.environment.working_path / file]
|
|
544
|
+
err = None
|
|
545
|
+
for file in candidates:
|
|
546
|
+
if not file.exists():
|
|
547
|
+
continue
|
|
548
|
+
with open(file, "r") as f:
|
|
549
|
+
command = f.read()
|
|
550
|
+
if file.suffix == ".sql":
|
|
551
|
+
return [self.execute_raw_sql(command)]
|
|
552
|
+
else:
|
|
553
|
+
return self.execute_text(command, non_interactive=non_interactive)
|
|
554
|
+
if err:
|
|
555
|
+
raise err
|
|
556
|
+
raise FileNotFoundError(f"File {file} not found")
|
|
557
|
+
|
|
558
|
+
def validate_environment(
|
|
559
|
+
self,
|
|
560
|
+
scope: ValidationScope = ValidationScope.ALL,
|
|
561
|
+
targets: Optional[list[str]] = None,
|
|
562
|
+
generate_only: bool = False,
|
|
563
|
+
) -> list[ValidationTest]:
|
|
564
|
+
from trilogy.core.validation.environment import validate_environment
|
|
565
|
+
|
|
566
|
+
return validate_environment(
|
|
567
|
+
self.environment, scope, targets, exec=None if generate_only else self
|
|
568
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from trilogy.core.models.execute import (
|
|
2
|
+
CTE,
|
|
3
|
+
QueryDatasource,
|
|
4
|
+
UnionCTE,
|
|
5
|
+
)
|
|
6
|
+
from trilogy.core.processing.nodes import StrategyNode
|
|
7
|
+
from trilogy.core.statements.author import (
|
|
8
|
+
MultiSelectStatement,
|
|
9
|
+
PersistStatement,
|
|
10
|
+
RowsetDerivationStatement,
|
|
11
|
+
SelectStatement,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseHook:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
def process_multiselect_info(self, select: MultiSelectStatement):
|
|
19
|
+
print("Multiselect with components:")
|
|
20
|
+
for x in select.selects:
|
|
21
|
+
self.process_select_info(x)
|
|
22
|
+
|
|
23
|
+
def process_select_info(self, select: SelectStatement):
|
|
24
|
+
print(f"Select statement grain: {str(select.grain)}")
|
|
25
|
+
|
|
26
|
+
def process_persist_info(self, persist: PersistStatement):
|
|
27
|
+
print(f"Persist statement persisting to {persist.address}")
|
|
28
|
+
self.process_select_info(persist.select)
|
|
29
|
+
|
|
30
|
+
def process_rowset_info(self, rowset: RowsetDerivationStatement):
|
|
31
|
+
print(f"Rowset statement with grain {str(rowset.select.grain)}")
|
|
32
|
+
|
|
33
|
+
def process_root_datasource(self, datasource: QueryDatasource):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
def process_root_cte(self, cte: CTE | UnionCTE):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
def process_root_strategy_node(self, node: StrategyNode):
|
|
40
|
+
pass
|